【Rust】edition 2024 移行ログ — cargo fix が直した8箇所と直さなかった1箇所

Rust の edition 2024 への移行を表したアイキャッチ画像。暗い背景に幾何学的な図形と光の帯
目次

この記事が解決する悩み

  • 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

上記の環境で実際に実行して確認した結果です。環境が異なる場合は挙動が変わることがあります。

Rust edition 2021 のクレートを 2024 に上げた実行ログ。cargo fix --edition が 8 箇所を自動修正したこと、edition を書き換えた直後に static mut への参照でビルドが止まること、&raw const に直して実行できること、末尾の一時値の寿命が変わる例を rustc の 2021 / 2024 で比べた結果までを1枚にまとめたもの
Rust edition 2021 のクレートを 2024 に上げた実行ログ。cargo fix –edition が 8 箇所を自動修正したこと、edition を書き換えた直後に static mut への参照でビルドが止まること、&raw const に直して実行できること、末尾の一時値の寿命が変わる例を rustc の 2021 / 2024 で比べた結果までを1枚にまとめたもの

あわせて読みたい

参考

【広告】このサイトはConoHa WINGで運営しています。安定した高速サーバーで快適にブログを書けています。いつもありがとう!!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次