この記事が解決する悩み
- edition 2021 のクレートを 2024 に上げたいけど、どこが壊れるのか見当がつかない
cargo fix --editionを回したのにcargo buildが通らず、次に何をすればいいか分からない- 「自動で直る」と聞いていたのに、手作業が残るのはなぜか知りたい
対象読者: Rust 2018 / 2021 のクレートを保守していて、edition 2024 への移行を検討しているエンジニア。
前提知識: Cargo.toml の edition が何を決めているか、unsafe ブロックと static mut の基本が分かっていれば読めます。
結論
edition 2024 への移行は、cargo fix --edition → Cargo.toml の書き換え → cargo check の3ステップです。自動修正は万能ではなく、実測では 6 種類・8 箇所が直ったあとに static mut への参照が1箇所残りました。
cargo fix --edition --allow-dirty --allow-no-vcs
# 実行結果(抜粋)
# Migrating Cargo.toml from 2021 edition to 2024
# Migrating src\main.rs from 2021 edition to 2024
# Fixed src\main.rs (8 fixes)
# Cargo.toml の edition は手で書き換える(この実行では 2021 のままだった)
sed -i 's/edition = "2021"/edition = "2024"/' Cargo.toml
cargo check
# 実行結果(抜粋)
# error: creating a shared reference to mutable static
# --> src\main.rs:26:5
# 26 | &TOTAL
# help: use `&raw const` instead to create a raw pointer
# error: could not compile `legacy-app` (bin "legacy-app") due to 1 previous error
edition 2024 は Rust 1.85(2025年2月)で安定化した、いまのところ最新のエディションです。移行そのものは数分で終わる作業なので、壊れる箇所さえ把握できていれば怖くありません。
検証に使ったクレート
edition 2021 のままビルドが通る小さなクレートを用意し、そこに「edition をまたぐと壊れる書き方」を詰め込みました。
use std::env;
static mut TOTAL: u32 = 0;
#[no_mangle]
pub extern "C" fn counter() -> u32 {
unsafe { TOTAL }
}
extern "C" {
fn getpid() -> i32;
}
// edition 2021 では `gen` は普通の識別子として使える
fn make_gen() -> usize {
7
}
unsafe fn read_len(buf: &[u8]) -> usize {
let gen = make_gen();
TOTAL += 1;
buf.len() + gen
}
unsafe fn total_ref() -> &'static u32 {
&TOTAL
}
fn bump_middle(values: &mut [u32; 3]) -> u32 {
match &mut values[..] {
[_, ref mut middle, _] => {
*middle += 1;
*middle
}
_ => 0,
}
}
fn main() {
env::set_var("APP_ENV", "dev");
let mut values = [1, 2, 3];
unsafe {
read_len(b"abc");
}
println!("{:?}", values);
println!("{}", bump_middle(&mut values));
println!("{}", unsafe { *total_ref() });
println!("{}", counter());
println!("{}", env::var("APP_ENV").unwrap());
}
// 実行結果(cargo check: edition 2021)
// warning: function `getpid` is never used
// warning: creating a shared reference to mutable static
// warning: `legacy-app` (bin "legacy-app") generated 2 warnings
// Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.13s
警告は出ますが、edition 2021 ではビルドが通ります。この状態を出発点にしますね。
cargo fix が自動で直した8箇所
cargo fix --edition は rust-2024-compatibility の lint 群を回して、機械的に直せる箇所を書き換えます。今回書き換わったのは次の6種類です。
| 壊れる書き方 | 修正後 |
|---|---|
#[no_mangle] |
#[unsafe(no_mangle)] |
extern "C" { } |
unsafe extern "C" { } |
| unsafe fn の本体にある unsafe 操作 | 本体全体を unsafe { } でラップ |
識別子 gen |
r#gen(宣言と使用の両方) |
暗黙の借用パターン内の ref mut |
冗長な ref mut を削除 |
env::set_var(...) |
unsafe { env::set_var(...) } + TODO コメント |
実際の差分はこうなりました。
-#[no_mangle]
+#[unsafe(no_mangle)]
-extern "C" {
+unsafe extern "C" {
-unsafe fn read_len(buf: &[u8]) -> usize {
- let gen = make_gen();
+unsafe fn read_len(buf: &[u8]) -> usize { unsafe {
+ let r#gen = make_gen();
- [_, ref mut middle, _] => {
+ [_, middle, _] => {
- env::set_var("APP_ENV", "dev");
+ // TODO: Audit that the environment access only happens in single-threaded code.
+ unsafe { env::set_var("APP_ENV", "dev") };
gen は将来の gen ブロック構文のために予約キーワードになったので、自動で r#gen に置き換わります。ビルドは通りますが読みにくいので、あとから generated のような普通の名前に直すのがおすすめですよ。
cargo fix が直さなかった1箇所
static mut への参照だけは、自動修正の対象外です。edition を上げた瞬間に、警告だった static_mut_refs がエラーに変わってビルドが止まります。
cargo check
# 実行結果(抜粋)
# warning: function `getpid` is never used
# error: creating a shared reference to mutable static
# --> src\main.rs:26:5
# 26 | &TOTAL
# | ^^^^^^ shared reference to mutable static
# help: use `&raw const` instead to create a raw pointer
# 26 | &raw const TOTAL
# error: could not compile `legacy-app` (bin "legacy-app") due to 1 previous error
公式のエディションガイドにも「There is no automatic migration to fix these references to static mut.」と明記されています。つまりここは必ず手作業になります。
直し方は、参照ではなく生ポインタにして、参照が必要な場所だけ安全に戻す形です。
// 修正前: static mut への参照を関数の外に持ち出していた
unsafe fn total_ref() -> &'static u32 {
&TOTAL
}
// 修正後: 生ポインタを取り、参照が要る所でだけ unsafe に戻す
fn total_ptr() -> *const u32 {
&raw const TOTAL
}
fn main() {
println!("{}", unsafe { *total_ptr() });
}
// 実行結果
// [1, 2, 3]
// 3
// 1
// 1
// dev
static mut を持つこと自体を見直せるなら、AtomicU32 に置き換えるのが一番きれいです。参照を配る必要がなくなり、unsafe も消えます。
番外: edition 2024 で通るようになった書き方
移行は「壊れる」だけではありません。ブロック末尾の式で作った一時値の寿命が変わり、これまで通らなかった書き方が通るようになった例があります。
use std::cell::RefCell;
fn last_item_count() -> usize {
let items = RefCell::new(vec![10, 20, 30]);
items.borrow().len()
}
// rustc --edition=2021 の出力
// error[E0597]: `items` does not live long enough
//
// rustc --edition=2024 の出力
// (コンパイル成功)
2021 では一時値の借用が末尾まで生きて items のドロップと衝突していました。2024 は一時値を先に落とすので、この形がそのまま書けます。
つまずきポイント
cargo fix --editionは Cargo.toml を書き換えない。 ログには Migrating Cargo.toml と出ますが、実行後に見ると edition は 2021 のままでした。忘れると「直したのに反映されない」と悩むことになります。- unsafe fn のラップは機械的なので、中身の安全性は自分で確認する。 本体を丸ごと
unsafe { }で包むだけの修正なので、その unsafe 操作が本当に安全かどうかは人間の責任のままです。 env::set_varは unsafe で包めば解決、とは限らない。 公式ドキュメントの Safety 節では、Unix 系のマルチスレッドプログラムでは使わない以外に安全な選択肢がないと書かれています。子プロセスに渡すだけならCommand::envに置き換えましょう。- 2021 で警告1件だったコードが、2024 でエラーになる。
static_mut_refsは deny-by-default なので、移行前に警告を全部潰しておくと差分が小さくなります。
まとめ
- 自動で直る: unsafe 属性・unsafe extern・unsafe fn の本体・
gen識別子・パターン内の冗長なref mut・env::set_var - 手で直す:
static mutへの参照(&raw constに置き換えるかAtomicU32へ移行) - 移行の順番:
cargo fix --edition→ Cargo.toml の edition →cargo checkで残りを手直し
手作業が残るのは static mut まわりだけなので、そこを先に洗い出しておけば移行はほぼ機械的に終わります。
【広告】お名前.comならドメイン取得が格安。![]()
検証環境
| OS | Windows 11 (build 10.0.26200) |
|---|---|
| CPU | AMD Ryzen 9 PRO 8945HS w/ Radeon 780M Graphics |
| メモリ | 28GB |
| 言語/ツール | rustc 1.95.0(59807616e 2026-04-14) / cargo 1.95.0(f2d3ce0bd 2026-03-21) |
| 使用コマンド | cargo fix –edition –allow-dirty –allow-no-vcs / cargo check / cargo run / rustc –edition=2021,2024 –emit=metadata |
| 測定日 | 2026-09-29 |
上記の環境で実際に実行して確認した結果です。環境が異なる場合は挙動が変わることがあります。

あわせて読みたい
参考
- Transitioning an existing project to a new edition(Rust Edition Guide 公式)
- Advanced migration strategies(Rust Edition Guide 公式)
- Announcing Rust 1.85.0 and Rust 2024(Rust Blog 公式)
- Disallow references to static mut(Rust Edition Guide 公式)
- gen keyword(Rust Edition Guide 公式)
- Unsafe attributes(Rust Edition Guide 公式)
- Unsafe extern blocks(Rust Edition Guide 公式)
- Match ergonomics reservations(Rust Edition Guide 公式)
- Tail expression temporary scope(Rust Edition Guide 公式)
- std::env::set_var(Rust 標準ライブラリ公式)
- std::sync::atomic(Rust 標準ライブラリ公式)
【広告】このサイトはConoHa WINGで運営しています。安定した高速サーバーで快適にブログを書けています。いつもありがとう!!![]()

コメント