序文

Rust エディションガイドへようこそ! 「エディション」とは、Rust に後方互換性が保てなくなるようなアップデートを行うための方法です。

このガイドでは、下記の項目について説明します:

  • エディションとは何か
  • 各エディションの変更内容
  • コードをあるエディションから別のエディションへ移行する方法

エディションとは?

Rust 1.0 のリリースでは、Rust のコア機能として「よどみない安定性」が提供されるようになりました。 Rust は、1.0 のリリース以来、いちど安定版にリリースされた機能は、将来の全リリースに渡ってサポートし続ける、というルールの下で開発されてきました。

一方で、後方互換でないような小さい変更を言語に加えることも、ときには便利です。 最もわかりやすいのは新しいキーワードの導入で、これは同名の変数を使えなくします。 例えば、Rust の最初のバージョンには asyncawait といったキーワードはありませんでした。 後のバージョンになってこれらを突然キーワードに変えてしまうと、例えば let async = 1; のようなコードが壊れてしまいます。

このような問題を解決するために、エディションという仕組みが使われています。 後方互換性を失わせるような機能をリリースしたいとき、我々はこれを新しいエディションの一部として提供します。 エディションはオプトイン、すなわち導入したい人だけが導入できるので、既存のクレートは明示的に新しいエディションに移行しない限りは変化を受けません。 すなわち、2018 以降のエディションを選択しない限り、たとえ最新バージョンの Rust であっても async はキーワードとして扱われません。 導入の可否はクレートごとに決めることができ、Cargo.toml への記載内容により決定されます。 cargo new コマンドで作成される新しいクレートは、常に最新の安定版のエディションでセットアップされます。

エディションはエコシステムを分断しない

エディションで最も重要な規則は、あるエディションのクレートと別のエディションでコンパイルされたクレートがシームレスに相互運用できるようになっているということです。 これにより、新しいエディションへ移行するかどうかは、他のクレートに影響を与えない「自分だけの問題」だと言えるのです。

クレートの相互運用性を守るために、我々がエディションに加えられる変更にはある種の制限がかかります。 一般に、エディションに加えられる変更は「表面上の」ものになりがちです。 エディションに関わらず、すべての Rust のコードは最終的にはコンパイラの中で同じ内部表現に変換されるのです。

エディションの移行は簡単で、ほとんど自動化されている

我々は、クレートを新しいエディションにアップグレードするのが簡単になるよう目指しています。 新しいエディションがリリースされるとき、我々は移行を自動化するツールも提供します。 このツールは、新しいエディションに適合させるために必要な小さな変更をコードに施します。 例えば、Rust 2018 への移行の際は、async と名のつく全てのものを、等価な生識別子構文である r#async へと書き換える、といった具合です。

この自動移行は必ずしも完璧とは限らず、手作業での変更が必要なコーナーケースもないとは言えません。 このツールは、コードの正しさやパフォーマンスに影響を与えうるような、プログラムの意味に関わる変更は避けるために全力を尽くします。

我々はこのツールの他に、エディションを構成する変更を取り扱っている、本エディション移行ガイドも管理しています。 このガイドでは、それぞれの変更内容と、もっと詳しく知りたい人向けのリンク、さらには知っておくべき詳細や重箱の隅まで網羅しています。 このガイドはエディションの概要を示すと同時に、自動化ツールに何らかの問題が生じたときのトラブルシューティング用の文献にもなります。

新しいプロジェクトを作成する

Cargoは新たなプロジェクトを作成する際に自動で最新のエディションをコンフィギュレーションに追加します。

> cargo +nightly new foo Created binary (application) `foo` project > cat foo/Cargo.toml [package] name = "foo" version = "0.1.0" authors = ["your name <you@example.com>"] edition = "2021" [dependencies]

この edition = "2021" によってあなたのパッケージが Rust 2021 を利用するように設定されます。 これ以外は必要ありません。

もし、他の古いエディションを使いたい場合は、その設定の値を変更できます。例えば、

[package] name = "foo" version = "0.1.0" authors = ["your name <you@example.com>"] edition = "2015" [dependencies]

とすると、あなたのパッケージは Rust 2015 でビルドされます。

既存のプロジェクトのエディションを移行する

Rust には、プロジェクトのエディションを進めるための自動移行ツールが付属しています。 このツールは、あなたのソースコードを書き換えて次のエディションに適合させます。 簡単にいうと、新しいエディションに進むためには次のようにすればよいです。

  1. cargo fix --edition を実行する
  2. Cargo.tomledition フィールドを新しいエディションに設定する。たとえば、 edition = "2021" とする
  3. cargo buildcargo test を実行して、修正がうまくいったことを検証する。

以下のセクションで、これらの手順の詳細と、その途中で起こりうる問題点について詳しく説明します。

我々は、新しいエディションへの移行をできるだけスムーズに行えるようにしたいと考えています。 もし、最新のエディションにアップグレードするのが大変な場合は、我々はそれをバグとみなします。 もし移行時に問題があった場合にはバグ登録してください。 よろしくお願いします!

訳注:Rustの日本語コミュニティもあります。 Slackを使用しておりこちらから登録できます。

移行の開始

例えば、2015エディションから2018エディションに移行する場合を見てみましょう。 ここで説明する手順は、例えば2021エディションのように、別のエディションに移行する場合も実質的に同様です。

src/lib.rsに以下のコードがあるクレートがあるとします。

#![allow(unused)] fn main() { trait Foo { fn foo(&self, i32); } }

このコードは i32 という無名パラメータを使用しています。 これは Rust 2018ではサポートされておらず、コンパイルに失敗します。 このコードを更新してみましょう。

あなたのコードを新しいエディションでコンパイルできるようにする

あなたのコードは新しいエディションに互換性のない機能を使っているかもしれないし、使っていないかもしれません。 Cargo には cargo fix というサブコマンドがあり、これがあなたのコードを自動的に更新して次のエディションへの移行を補助してくれます。 まず初めに、これを実行してみましょう。

cargo fix --edition

これはあなたのコードをチェックして、自動的に移行の問題を修正してくれます。 もう一度 src/lib.rsを見てみましょう。

#![allow(unused)] fn main() { trait Foo { fn foo(&self, _: i32); } }

i32 値をとるパラメータに名前が追加された形でコードが書き換えられています。 この場合は、パラメータ名がなかったので、使用されていないパラメータの慣習に従って _ を付加しています。

cargo fixは常に自動的にコードを修正してくれるわけではありません。 もし、cargo fixがコードを修正できない時にはコンソールに修正できなかったという警告を表示します。 その場合は手動でコードを修正してください。 「発展的な移行戦略の章では、移行に関するより多くの情報があります。また、このガイドの他の章では、どのような変更が必要かについても説明しますので、併せてご参照ください。 問題が発生したときは、ユーザーフォーラム で助けを求めてください。

新機能を使うために新たなエディションを有効化する

新しいエディションの新機能を使うには明示的にオプトインする必要があります。 準備がよければ、Cargo.toml に新しい edition のキーバリューペアを追加してください。 例えば以下のような形になります。

[package] name = "foo" version = "0.1.0" edition = "2018"

もし edition キーがなければCargoはデフォルトで Rust 2015をエディションとして使います。 しかし、上記の例では 2018 を明示的に指定しているので、コードは Rust 2018 でコンパイルされます!

次に、新しいエディション上であなたのプロジェクトをテストしましょう。 cargo test を実行するなどして、プロジェクトのテストを走らせ、すべてが元のまま動くことを確認してください。 新たに警告が出た場合、(--edition なしの) cargo fix をもう一度実行することで、コンパイラからの提案を受け入れてみるのも良いかもしれません。

わーい。今やあなたのコードは Rust 2015 と Rust 2018 の両方で有効です!

発展的な移行戦略

移行ツールの仕組み

cargo fix --edition コマンドは、cargo check コマンドと同様のコマンドを、次のエディションでコンパイルされなくなるコードを検知する特別なリントが有効になった状態で実行することで機能します。 このリントには、コードを変更したら現在と次のエディションの双方に適合させるための指示も含まれています。 cargo fix コマンドはソースコードをそれに従って変更し、再び cargo check を実行して修正がうまく行ったか確認します。 うまく行かなかった場合、変更を巻き戻して警告を表示します。

現在と次のエディションの両方に同時に適合したコードに書き換えると、コードを段階的に移行することが楽になります。 自動移行が完全には成功しなかったか、手作業で変えてやる必要がある場合は、元ののエディション留まったまま同じことを繰り返してから、Cargo.toml を編集して次のエディションに進めてもよいです。

cargo fix --edition が適用するリントは、リントグループの一部です。 例えば、2018 から 2021 に移行する場合、Cargo は rust-2021-compatibility というリントグループをコードの修正に使用します。 それぞれのリントを移行に役立てるコツについては、後の「部分的な移行」の章をご覧ください。

cargo fix は、cargo check を複数回実行する可能性があります。 たとえば、一通りの修正を終えても、修正によって新たな警告が出て、さらなる修正が必要になるかもしれません。 Cargo は、新しく警告が出なくなるまでこれを繰り返し続けます。

複数の設定を移行する

cargo fix は一度に1つのコンフィギュレーションでしか動きません。 Cargo のフィーチャ条件付きコンパイルを使用している場合、cargo fix を異なるフラグで複数回実行する必要があるかもしれません。

例えば、あなたのコードが #[cfg] を使ってプラットフォームによって違うコードを含むようになっていた場合、cargo fix--target オプションつきで実行して、ターゲットごとに修正をする必要があるかもしれません。 クロスコンパイルができない場合、コードを別のマシンに移して作業せざるを得ないかもしれません。

同様に、フィーチャによる条件分岐、例えば #[cfg(feature = "my-optional-thing")] のようなものがある場合、--all-features フラグを使って、フィーチャの壁を超えて cargo fix がすべてのコードを変更できるようにするとよいでしょう。 フィーチャごとに別々にコードを移行したい場合は、--features フラグを使って一つずつ移行作業をすることもできます。

巨大なプロジェクトやワークスペースの移行

大きなプロジェクトで問題が発生した場合、作業を単純にするために、段階的に移行していくこともできます。

Cargo のワークスペースでは、エディションはパッケージごとに定義されているため、自然とパッケージごとに1つずつ移行をすることになります。

Cargo のパッケージにおいては、全パッケージを同時に移行することも、Cargo ターゲットごとに1つずつ移行することもできます。 例えば、パッケージに複数のバイナリ、テスト、サンプルコード(example ターゲット)がある場合、cargo fix --edition コマンドに特定のターゲット選択用のフラグを組み合わせることで、一つのターゲットだけを移行することもできます。 デフォルトでは、cargo fix--all-targets が暗黙に指定されていると扱います。

より発展的には、Cargo.toml 中に各ターゲットで使用するエディションを指定することもできます:

[[bin]] name = "my-binary" edition = "2018"

おそらく普通はこれは必要ありませんが、ターゲットがたくさんあって全部いっぺんに移行作業できないような場合には一つの選択肢になるでしょう。

壊れたコードを元に部分的に移行する

ときどき、コンパイラに提案された修正ではうまくいかないことがあります。 すると、Cargo は何が起こったかとどんなエラーが出たかを示す警告を報告しますが、デフォルトでは Cargo は変更を巻き戻します。 しかし、Cargo にはコードを壊れたままにしておいてもらい、手作業で問題を解決する、というのも有効な手段です。 ほとんどの修正は正しいかもしれませんし、壊れた修正もだいたい正しくて、少しいじれば問題ないかもしれないのです。

そんなときは、cargo fix コマンドを実行するときに --broken-code オプションをつけて、Cargo が変更を巻き戻さないようにできます。 そうすれば、エラーを実際に見てみることも、修正点を確認することもできます。

プロジェクトを段階的に移行するもう一つの方法は、それぞれの修正を一つずつ適用することです。 このためには、各リントを警告として追加して、(--edition なしの)cargo fix を実行するか、お手持ちのエディタや IDE が「クイックフィックス」をサポートしていればそれを使って提案を適用すればよいです。

例えば、2018 エディションには keyword-idents という、キーワードとの衝突をすべて修正するためのリントがあります。 各クレートのトップ(例えば src/lib.rssrc/main.rs の先頭)に #![warn(keyword_idents)] を追加して、cargo fix を実行すれば、そのリントによる提案だけを受け入れることができます。

各エディションで有効化されるリントの一覧は、リントグループのページを見るか、 rustc -Whelp コマンドを実行すれば確認できます。

マクロの移行

マクロの中には、エディションを進めるにあたって手作業が必要なものがあります。 例えば、cargo fix --edition は、次のエディションで動作しない文法を生成するマクロを自動修正することは難しいかもしれません。

これは、手続き型マクロmacro_rules を使ったマクロの双方で問題になります。 macro_rules を使ったマクロは、マクロが同じクレートに属していたら自動でアップデートできる場合もありますが、いくつかの状況ではできません。 手続き型マクロは原則、全く修正できないと言っていいでしょう。

例えば、この(わざとらしい)マクロ foo を含むクレートを 2015 から 2018 に移行しようとしても、foo は自動修復されません。

#![allow(unused)] fn main() { #[macro_export] macro_rules! foo { () => { let dyn = 1; println!("it is {}", dyn); // "これは {} です" }; } }

マクロが 2015 のクレートで定義されている場合、マクロの衛生性(後述)のおかげでこれは他のエディションのクレートからも使用することができます。 2015 では、dyn は通常の識別子で、制限なく使用できます。

一方で、2018 では、dyn はもはや正当な識別子ではありません。 cargo fix --edition で 2018 へ移行するとき、Cargo は警告やエラーを一切表示しません。 しかし、foo はどのクレートで呼び出されても動作しません。

あなたのコードにマクロがある場合、そのマクロの構文を十分に網羅するテストがあることが推奨されます。 また、そのマクロを複数のエディションのクレートの中でインポートして使用し、どこでも動くということを確認するのも良いでしょう。 問題に突き当たった場合、このガイドの本章に目を通して、全エディションで動作するようにするためにどうすればいいか理解する必要があります。

マクロの衛生性

マクロには、「エディション衛生性」と呼ばれる仕組みが使われています。このシステムでは、マクロ内のトークンがどのエディションから来たかが記録されています。 これにより、呼び出す側のエディションがどれであるかを気にせずに、外部のマクロを呼び出すことができます。

上で例に出した、macro_rules で定義されたマクロを詳しく見てみましょう。 このマクロは dyn を識別子に使用しています。 このマクロが定義されているのが 2015 エディションのクレート内なのならば、これは問題なく動きます。 さらに、2018 エディションでは dyn はキーワードで普通は識別子に使えませんが、2018 エディションのクレートからこのマクロを呼び出したとしても大丈夫です。 パーサーはトークンのエディションに着目して、どう解釈したらよいかを判断します。

問題が起こるのは、マクロが定義されている側のクレートのエディションを 2018 に変更したときです。 今や、これらのトークンは 2018 エディションのものとしてタグ付けされているために、パースに失敗します。 しかしながら、このマクロは自身のクレートから呼び出されてもいないために、cargo fix --edition はこれを検査することも修正することもできません。

ドキュメンテーションテスト

現在のところ、cargo fixドキュメンテーションテストを更新することはできません。 Cargo.toml でエディションを更新したら、cargo test を実行して全てのテストに通過することを確認すべきです。 新しいエディションでサポートされない構文がドキュメンテーションテストに使われていた場合は、手作業で修正する必要があります。

まれなケースですが、エディションをテストごとに設定することもできます。 例えば、バッククォート3つの後に edition2018 アノテーションをつければ、rustdoc が使うエディションを指定できます。

生成されるコード

自動移行が使えない場所がもう一つあります。それは、ビルドスクリプトが Rust コードをコンパイル時に生成する場合です(具体例は「コード生成」をご参照ください)。 このとき、出てくるコードが次のエディションで機能しない場合は、生成されるコードが新しいエディションに適合するように、ビルドスクリプトを自分で書き換える必要があります。

Cargo でないプロジェクトの移行

プロジェクトのビルドシステムが Cargo 以外の場合でも、次のエディションへの移行に自動リントが利用できるかもしれません。 適切なリントグループを使って、前述の移行リントを有効化できます。 例えば、#![warn(rust_2021_compatibility)] というアトリビュートを使ったり、-Wrust-2021-compatibility--force-warns=rust-2021-compatibility などの CLI フラグを使用するとよいです。

次に、これらのリントをコードに適用します。 これにはいくつかの手があります:

  • 警告を読んで、コンパイラに提案された変更を自分で加える。
  • エディタや IDE の機能で、コンパイラからの提案を適用する。 例えば、Visual Studio CodeRust Analyzer 拡張 を使えば、自動的に提案を受け入れるための「クイックフィックス」が使えます。 他にも多くのエディタで同様の機能が使えます。
  • rustfix ライブラリを用いて、移行ツールを自作する。 このライブラリは Cargo 内部でも使われており、コンパイラからの JSON メッセージを元にソースコードを編集します。 ライブラリの使用例は、examples ディレクトリをご覧ください。

新しいエディションで慣用的な書き方をする

エディションは、新機能の追加や古い機能の削除のためだけのものではありません。 どんなプログラミング言語でも、その言語らしい書き方は時代によって変化します。Rust も同じです。 古いプログラムはコンパイルには通るかもしれませんが、今は別の書き方があるかもしれません。

例えば、Rust 2015 では、外部クレートは以下のように extern crate で明示的に宣言される必要がありました:

// src/lib.rs extern crate rand;

Rust 2018 では、外部クレートを使うのにextern crate は必要ありません

cargo fix には --edition-idioms オプションがあり、古い書き方(イディオム)の一部を新しい書き方に書き換えることができます。

警告: 現行の「イディオムリント」にはいくつか問題があることが知られています。 これらのリントはときどき、受け入れるとコンパイルできなくなるような誤った提案をすることがあります。 現在、以下のリントがあります。

以下の手順は、コンパイラや Cargo の多少のバグを厭わない恐れ知らずだけにしかお勧めできません!  不都合が発生する場合は、前述--broken-code オプションを使ってツールにやれるだけのことをさせ、残った問題を自分の手で解決してもよいでしょう。

ともあれ、先程の短いコードを Cargo に直してもらうにはこうすればよいです:

cargo fix --edition-idioms

すると、src/lib.rs に書かれた extern crate rand; が削除されます。

これで、自分でコードに手を下すことなく、コードを現代風にできました!

Rust 2015

Rust 2015は「安定性」というテーマを掲げています。 このエディションはRust 1.0のリリースから始まり、デフォルトのエディションとなっています。 エディションの仕組み自体は2017年末に考案されましたが、Rust 1.0は2015年5月にリリースされていて、「2015」が特定のエディションを指定しなかった時のデフォルトになります。

「安定性」がRust 2015エディションのテーマです。 なぜなら、Rust 1.0はRust開発に著しい変化をもたらしたからです。 Rust 1.0以前は、Rustは毎日のように変わっていました。 そのような言語は大規模なソフトウエア開発には使えないですし、学ぶことも難しいでしょう。 Rust 1.0とRust 2015エディションの登場とともに、我々は後方互換性にコミットし、Rust上で開発を行う人々のための強固な基盤を提供しています。

Rust 2015はデフォルトのエディションなのであなたのコードをRust 2015へポーティングするということはありません。 どんなRustのコードもRust 2015です。 あなたは Rust 2015から離れることはあっても、近づいていくということはありません。 ということで、これ以上あまり言うことはないでしょう!

Rust 2018

情報
RFC#2052 (このRFCはエディションシステムそのものも提案している)
リリースバージョン1.31.0

Rust 2018 のリリースのために、エディションシステムが作られました。 Rust 2018 のリリースは、生産性をテーマに掲げた数々の新機能とともにもたらされました。 ほとんどの新機能には後方互換性があり、すべてのエディションで使用可能となりました。 一方、一部の変更にはエディション機構が必要となりました(代表例はモジュールシステムの変更です)。

パスとモジュールシステムへの変更

導入 Rust バージョン: 1.31

概要

  • use 宣言中のパスが、他のパスと同じように扱われるようになりました。
  • :: から始まるパスの直後には、常に外部クレートが続くようになりました。
  • pub(in path) のような可視性修飾子[^1]において、パスは crate, self, super のいずれかで始まらなくてはならなくなりました。

[^1] pub(in path) 構文は、アイテムを path に指定したモジュール内だけに公開にするための可視性修飾子です。 例えば、pub(in crate::outer_mod) のように書くと crate::outer_mod モジュール内だけからアクセスできるようになります。("public in crate::outer_mod" という意味です。) 詳細は The Rust Reference (英語) の説明もご参照ください。

動機

Rust を習って間もない人にとって、モジュールシステムは最も難しいものであることが多いです。 もちろん、習得に時間がかかるものは誰にでもあります。 しかし、モジュールシステムが多くの人を混乱させる根本的原因は、モジュールシステムの定義そのものは単純で首尾一貫しているにも関わらず、その帰結が一貫性のない、非直感的な、不思議なものに感じられてしまうことにあります。

ですので、Rust の 2018 エディションにおけるモジュールシステムの新機能は、モジュールシステムを単純化し、仕組みをよりわかりやくするために導入されました。

簡単にまとめると、

  • extern crate は九割九分の場面で必要なくなりました。
  • crate キーワードは、自身のクレートを指します。
  • サブモジュール内であっても、パスをクレート名から始めることができます。
  • :: から始まるパスは、常に外部クレートを指します。
  • foo.rsfoo/ サブディレクトリは共存できます。サブディレクトリにサブモジュールを置く場合でも、mod.rs は必要なくなりました。
  • use 宣言におけるパスも、他の場所のパスと同じように書けます。

こうして並べられると、一見これらの新しい規則はてんでバラバラですが、これにより、総じて今までよりはるかに「こうすればこう動くだろう」という直感が通じるようになりました。 詳しく説明していきましょう!

詳しく説明

新機能を一つずつ取り上げていきます。

さようなら、extern crate

これは非常に単純な話です。 プロジェクトにクレートをインポートするために extern crate を書く必要はもはやありません。 今まで:

// Rust 2015 extern crate futures; mod submodule { use futures::Future; }

これから:

// Rust 2018 mod submodule { use futures::Future; }

今や、プロジェクトに新しくクレートを追加したかったら、Cargo.toml に追記して、それで終わりです。 Cargo を使用していない場合は、rustc に外部クレートの場所を --extern フラグを渡しているでしょうが、これを変える必要はありません。

一つ注意ですが、cargo fix は今の所この変更を自動では行いません。 将来、自動で変更がなされるようになるかもしれません。

例外

このルールには一つだけ例外があります。それは、"sysroot" のクレートです。 これらのクレートは、Rust に同梱されています。

通常は、これらが必要なのは非常に特殊な状況です。 1.41 以降、rustc--extern=CRATE_NAME フラグを受け付けるようになりました。 このフラグを指定すると、extern crate で指定するのと同じように、与えられたクレート名が自動的に追加されます。 ビルドツールは、このフラグを用いてクレートのプレリュードに sysroot のクレートを注入できます。 Cargo にはこれを表現する汎用的な方法はありませんが、proc_macro のクレートではこれが使用されます。

例えば、以下のような場合には明示的に sysroot のクレートをインポートする必要があります:

  • std: 通常は不要です。クレートに #![no_std] が指定されていない限り、std は自動的にインポートされるからです。
  • core: 通常は不要です。クレートに #![no_core] が指定されていない限り、core は自動的にインポートされるからです。 例えば、標準ライブラリに使用されている一部の内部クレートではこれが必要です。
  • proc_macro: 1.42 以降、proc-macro クレートではこれは自動的にインポートされます。 それより古いリリースをサポートしたい場合か、Cargo 以外のビルドツールを使っていてそれが rustc に適切な --extern フラグを渡さない場合は、extern crate proc_macro; と書く必要があります。
  • alloc: alloc クレート内のアイテムは、通常は std を通して公開されたものが使用されます。 アロケーションをサポートする no_std なクレートにおいては、明示的に alloc をインポートすることが必要になります。
  • test: これは nightly チャンネルでのみ使用可能で、まだ安定化されていないベンチマークのために主に使われます。

マクロ

extern crate のもう一つの使い道は、マクロのインポートでしたが、これも必要なくなりました。 マクロは、他のアイテムと同様、use でインポートできます。 たとえば、以下の extern crate は:

#[macro_use] extern crate bar; fn main() { baz!(); }

こんな感じに変えることができます:

use bar::baz; fn main() { baz!(); }

クレートの名前変更

今までは as を使ってクレートを違う名前でインポートしていたとします:

extern crate futures as f; use f::Future;

その場合、 extern crate の行を消すだけではうまくいきません。こう書く必要があります:

use futures as f; use self::f::Future;

f を使っているすべてのモジュールに対して、同様の変更が必要です。

crate キーワードは自身のクレートを指す

use 宣言や他のコードでは、crate:: プレフィクスを使って自身のクレートのルートを指すことができます。 たとえば、crate::foo::bar と書けば、それがどこに書かれていようと、foo モジュール内の bar という名前を指します。

:: というプレフィクスは、かつてはクレートのルートまたは外部クレートのいずれかを指していましたが、今は常に外部クレートを指します。 例えば、::foo::bar と書くと、これは常に foo というクレートの bar という名前を指します。

外部クレートのパス

かつては、use によるインポートなしで外部クレートを使用するには、:: から始まるパスを書かなくてはなりませんでした。

// Rust 2015 extern crate chrono; fn foo() { // this works in the crate root // クレートのルートでは、このように書ける let x = chrono::Utc::now(); } mod submodule { fn function() { // but in a submodule it requires a leading :: if not imported with `use` // 一方、サブモジュールでは `use` でインポートしない限り :: から始めないといけない let x = ::chrono::Utc::now(); } }

今は、外部クレートの名前はサブモジュールを含むクレート全体でスコープに含まれます。

// Rust 2018 fn foo() { // this works in the crate root // クレートのルートでは、このように書ける let x = chrono::Utc::now(); } mod submodule { fn function() { // crates may be referenced directly, even in submodules // サブモジュール内でも、クレートを直接参照できる let x = chrono::Utc::now(); } }

さようなら、mod.rs

Rust 2015 では、サブモジュールは:

// This `mod` declaration looks for the `foo` module in // `foo.rs` or `foo/mod.rs`. // この `mod` 宣言は、`foo` モジュールを `foo.rs` または `foo/mod.rs` のいずれかに探す mod foo;

foo.rsfoo/mod.rs のどちらにも書けました。 もしサブモジュールがある場合、必ず foo/mod.rs に書かなくてはなりませんでした。 したがって、foo のサブモジュール bar は、foo/bar.rs に書かれることになりました。

Rust 2018 では、サブモジュールのあるモジュールは mod.rs に書かなくてはならないという制限はなくなりました。 foo.rsfoo.rs のままで、サブモジュールは foo/bar.rs のままでよくなりました。 これにより、特殊な名前がなくなり、エディタ上でたくさんのファイルを開いても、mod.rs だらけのタブでなく、ちゃんとそれぞれの名前を確認することができるでしょう。

Rust 2015 Rust 2018
.
├── lib.rs
└── foo/
    ├── mod.rs
    └── bar.rs
.
├── lib.rs
├── foo.rs
└── foo/
    └── bar.rs

use におけるパス

導入 Rust バージョン: 1.32

Rust 2018 では、Rust 2015 に比べてパスの扱いが単純化・統一されています。 Rust 2015 では、use 宣言におけるパスは他の場所と異なった挙動を示しました。 特に、use 宣言におけるパスは常にクレートのルートを基準にしたのに対し、プログラム中の他の場所でのパスは暗黙に現在のスコープが基準になっていました。 トップレベルモジュールではこの2つに違いはなかったので、プロジェクトがサブモジュールを導入するほど大きくない限りはすべては単純に見えました。

Rust 2018 では、トップレベルモジュールかサブモジュールかに関わらず、use 宣言でのパスと他のプログラム中のパスは同じように使用できます。 現在のスコープからの相対パスも、外部クレート名から始まるパスも、crate, super, self から始まるパスも使用できます。

今まではこう書いていたコードは:

// Rust 2015 extern crate futures; use futures::Future; mod foo { pub struct Bar; } use foo::Bar; fn my_poll() -> futures::Poll { ... } enum SomeEnum { V1(usize), V2(String), } fn func() { let five = std::sync::Arc::new(5); use SomeEnum::*; match ... { V1(i) => { ... } V2(s) => { ... } } }

Rust 2018 でも全く同じように書けます。ただし、extern crate の行は消すことができます。

// Rust 2018 use futures::Future; mod foo { pub struct Bar; } use foo::Bar; fn my_poll() -> futures::Poll { ... } enum SomeEnum { V1(usize), V2(String), } fn func() { let five = std::sync::Arc::new(5); use SomeEnum::*; match ... { V1(i) => { ... } V2(s) => { ... } } }

サブモジュール内でも、コードを全く変えずに、同じように書けます。

// Rust 2018 mod submodule { use futures::Future; mod foo { pub struct Bar; } use foo::Bar; fn my_poll() -> futures::Poll { ... } enum SomeEnum { V1(usize), V2(String), } fn func() { let five = std::sync::Arc::new(5); use SomeEnum::*; match ... { V1(i) => { ... } V2(s) => { ... } } } }

これにより、コードを他の場所に移動することが簡単になり、マルチモジュールなプロジェクトがより複雑になるのを防止できます。

もし、例えば外部モジュールとローカルのモジュールが同名であるなど、パスが曖昧な場合は、エラーになります。 その場合、他と衝突している名前のうち一方を変更するか、明示的にパスの曖昧性をなくす必要があります。 パスの曖昧性をなくすには、::name と書いて外部クレート名であることを明示するか、self::name と書いてローカルのモジュールやアイテムであることを明示すればよいです。

トレイト関数の匿名パラメータの非推奨化

導入 Rust バージョン: 1.31

概要

詳細

RFC #1685 に基づいて、トレイト関数のパラメータを匿名にすることはできなくなりました。

例えば、2015 エディションでは、以下のように書けました:

#![allow(unused)] fn main() { trait Foo { fn foo(&self, u8); } }

2018 エディションでは、すべての引数に(ただの _ であってもいいので、何らかの)名前がついていなければなりません:

#![allow(unused)] fn main() { trait Foo { fn foo(&self, baz: u8); } }

新しいキーワード

導入 Rust バージョン: 1.27

概要

動機

トレイトオブジェクトを表す dyn Trait

dyn Trait 機能は、トレイトオブジェクトを使うための新しい構文です。簡単に言うと:

  • Box<Trait>Box<dyn Trait> になり、
  • &Trait&mut Trait&dyn Trait&mut dyn Trait になる

といった具合です。プログラム内では:

#![allow(unused)] fn main() { trait Trait {} impl Trait for i32 {} // old // いままで fn function1() -> Box<Trait> { unimplemented!() } // new // これから fn function2() -> Box<dyn Trait> { unimplemented!() } }

これだけです!

なぜ?

トレイトオブジェクトにトレイト名をそのまま使うのは悪手だったと、後になって分かりました。 今までの構文は、経験者にとってさえ往々にして曖昧にして難解で、代替機能を使うべきで本来お呼びでないような場面[^1]で頻繁に使われ、時には遅く、代替機能にはできることができないのです。

その上、impl Trait が入ったことで、「impl Traitdyn Trait か」の関係はより対称的になり、「impl TraitTrait か」よりちょっといい感じです。 impl Trait の説明はこちらです。

したがって、新しいエディションでは、トレイトオブジェクトが必要なときは、ただの Trait でなく dyn Trait を使うべきです。

[^1] 訳注: 原文ではこの文は、本ページで説明する新構文を提案する RFC から抜粋された文章になっています。 特に脚注で示した箇所は、原文では "favors a feature that is not more frequently used than its alternatives" とあり、その文意は同 RFC に解説されています。以下では、それを要約します。
特定のトレイトを実装した異なる型を共通して扱いたいとき、大抵はトレイトオブジェクトを使う必要はありません。 単一のコンテナに複数の型の構造体を入れたい場合、enum を使えばよいです。 関数の返り値が特定のトレイトを実装していると示すには、impl Trait 構文を使えばよいです。 特定のトレイトを実装する任意の型を関数の引数や構造体のフィールドにした場合、ジェネリクスを使えばよいです。 大抵の場合は、このようにセマンティクス面からもパフォーマンス面からもより適切な代替案があり、トレイトオブジェクトの出る幕はありません。
トレイトオブジェクトが真に必要なのは、これより複雑なことをしたい場合だけです。 しかし、Rust 2015 では、&Trait のように書くだけで、「気軽に」トレイトオブジェクトが作れてしまうという罠がありました。 そこで、Rust 2018 では、どうしてもトレイトオブジェクトを作りたい場合は &dyn Trait 構文を使用することが必要になりました。

asyncawait

これらのキーワードは Rust に非同期の機能を実装するために予約されました。非同期の機能は最終的に 1.39.0 でリリースされました

キーワード try

キーワード trytry ブロックで使うために予約されましたが、(これを書いている時点で)まだ安定化されていません(追跡イシュー

推論変数への生ポインタに対するメソッドのディスパッチ

概要

1

(訳注) ハードエラーとは、#[allow(...)] などを使って、ユーザーが無効化することができないエラーのことです。 例えば、ライフタイムに不整合があったとします。ユーザーが実際はそれが安全であると信じていても、そのようなコードをコンパイルすることは許されていません。 #[allow(...)] による上書きも不可能となっています。これがハードエラーです。 一方、到達不能コード(たとえば関数の途中で return をしており、それ以降のコードは決して実行されないような状況)は、コンパイル自体は可能で、時には役に立つこともあるので、ユーザーが #[allow(dead_code)] と書くことで警告を抑制できます。これはハードエラーではありません。 詳細は rustc book の説明(英語) もご参照ください。

詳細

詳細は Rust のイシュー #46906 を参照してください。

訳注: 詳しく解説します。以下のプログラムをご覧ください2

let s = libc::getenv(k.as_ptr()) as *const _; s.is_null()

1行目の libc::getenv は、*mut c_char 型の生ポインタを返す関数です。 このポインタは as を使って *const ポインタに変換できますが、その際これが「何の型のポインタであるか」を _ にて省略しています。

2行目では、*const _ である s に対して .is_null() を呼び出しています。 任意の型 T について、プリミティブ型 *const Tis_null という固有メソッドを持つので、ここで呼び出されるのはこの固有メソッドです。

問題はこの後です。 現在はメソッド3を呼べる型は一部の型に限られていますが、 将来それを任意の型に拡張しようという提案が出ています。 この新機能は "arbitrary self types" (self の型の任意化)と呼ばれます。 しかし、これが導入されると困ったことが起きます。

次のような構造体があったとしましょう2:

#![feature(arbitrary_self_types)] struct MyType; impl MyType { // この関数が問題 fn is_null(self: *mut Self) -> bool { println!("?"); true } }

すると、最初のプログラムの2行目 s.is_null はどうなるでしょうか? 変数 s はキャストによって *const _ 、つまり「何かの型への生定数ポインタ」を意味していました。 そして今や、 is_null として呼び出せる関数は2つあります。 1つは先程の *const T に対して実装された is_null、 もう一つは今 *const MyType に対して実装された is_null です。 つまり、メソッドの呼び出しに曖昧性が生じています。

この問題の解決策は簡単です。キャスト後の型がどの定数ポインタになるのか明示すればよいです2:

let s = libc::getenv(k.as_ptr()) as *const libc::c_char; s.is_null()

こうすることで、is_null の候補は *const T だけになります。 libc::c_char は他のクレートで定義された型ですので、 この型に対して新しくメソッドが実装されることはなく、恒久的に曖昧性がなくなります。

こうした理由から、 *const _*mut _ など、「未知の型への生ポインタ」に対してメソッドを呼び出すと、コンパイラがそれを検知するようになりました。 最初は警告リントとして導入されましたが、Rust 2018 エディションでハードエラーに格上げされました。これが、本ページで説明されている変更点です。

2

これらのソースコードは mikeyhew 氏による rust-lang/rust#46906 へのコメントより引用されたものです。ただし、コメントが著者によって追加されています。

3

関連関数のうち、第一引数が self であるものは、メソッド呼び出し演算子(.)を用いて呼び出すことができます。 このような関連関数をメソッドと呼びます。 s.is_null() と書くと、これは s に対してメソッド is_null(...) を呼び出していることになります。 (参考

訳注: タイトルにある「推論変数」とは、英語では inference variable または existencial variable と呼ばれます。 Rust には型をある程度明示しなくても自動的に決定する機能(型推論)があります。 型推論とは、非常に単純に説明すると、未知の型を変数、プログラム中から得られる手がかりを条件とみなした「型の連立方程式」を解く事に当たります。 推論変数とは、この方程式における変数のことです。 今回は *const _、つまり「未知の型 _ に対する定数生ポインタ」が出てきています。この _ が「推論変数」にあたります。 詳しくは、rustc book の型推論の説明 (英語)もご参照ください。

Cargo への変更

概要

  • Cargo.toml にターゲットの指定がある場合であっても、他のターゲットの自動探索がされなくなるということはなくなりました[^1]。
  • path フィールドが指定されていないターゲットに対して、src/{target_name}.rs の形のターゲットパスは自動推定されなくなりました[^2]。
  • 現在のディレクトリに対して cargo install できなくなりました。現在のパッケージをインストールしたい場合は cargo install --path . と指定する必要があります[^3]。

[^1] 訳注: Cargo は、Cargo.toml 内に明示的に指定されていなくても、フォルダ構成の慣習に従っているファイルに関しては、自動でターゲットに追加します。 例えば、src/bin/my_application.rs というファイルがプロジェクト内に存在したら、Cargo.toml[[bin]] セクションで my_application の存在が宣言されていなくても、自動的にこのファイルがバイナリターゲット(つまり、cargo run --bin my_application として実行できるもの)としてビルドされるようになっています。 これは、ターゲットの自動探索と呼ばれています。
Rust 2015 と Rust 2018 の違いは、Cargo.toml に明示的にターゲットが1つ以上宣言されている場合(つまり、[lib], [[bin]], [[test]], [[bench]], [[example]] のどれかが1つ以上ある場合)に生じます。 Rust 2015 では、これらのうちどれか1つが指定されていた場合、ターゲットの自動探索は無効化されました。 Rust 2018 では、その場合であっても、ターゲットの自動探索は無効化されません。
詳細は、Cargo Book の "Target auto-discovery"(英語)もご覧ください。

[^2] 訳注: Cargo は、ターゲットを宣言するセクション(つまり、例えば [lib] セクションや [[bin]] セクション)に path フィールドがなかった場合、 name フィールドとフォルダ構成の慣習に従ってパスを推論し、そのパスが存在すればそれを使用します。 たとえば、Cargo.toml

[[bin]] name = "my_binary"

というセクションがあった場合、path フィールドが src/bin/my_binary.rs であると自動的に判断します。 そして、src/bin/my_binary.rs が存在すれば、これをターゲットのソースとして採用することになります。
Rust 2015 では、これに加えて pathsrc/my_binary.rs である可能性も候補になりました。 すなわち、上の状況において src/bin/my_binary.rs が存在しないが src/my_binary.rs が存在する場合は、src/my_binary.rs をターゲットのソースとして採用しました。 しかし、この挙動は時にわかりにくいエラーを生むため、この形のパスが候補とされる挙動を利用することは非推奨となり、Rust 2018 では挙動そのものが廃止されました。

[^3] 訳注: cargo install コマンドは、指定したクレートのバイナリターゲットをインストールする、つまりビルドして実行ファイルを所定の場所に配置するためのコマンドです。 Rust 2015 では、カレントディレクトリが Cargo プロジェクト下であったときに cargo install とだけ実行すると、そのプロジェクトに含まれるクレートを対象としてインストールが実行されました。 Rust 2018 ではこの挙動は廃止され、cargo install --path . と明示的にカレントディレクトリのクレートをインストールの対象にすると宣言しなければならなくなりました。

Rust 2021

情報
RFC#3085
リリースバージョン1.56.0

Rust 2021 エディションでは、新機能を追加し、言語をより一貫したものにして、 さらに将来の拡張性の余地を広げるための、いくつかの変更がなされています。 以下の章ではこれらの変更の詳細を見ていくと同時に、 既存のコードを移行するためのガイドも示していきます。

Prelude への追加

概要

  • TryInto, TryFrom, FromIterator トレイトがプレリュードに追加されました。
  • これにより、トレイトメソッドへの呼び出しに曖昧性が発生して、コンパイルに失敗するようになるコードがあるかもしれません。

詳細

標準ライブラリの prelude モジュールには、 すべてのモジュールにインポートされるものが余すことなく定義されています。 そこには、Option, Vec, drop, Clone などの、頻繁に使われるアイテムが含まれます。

Rust コンパイラは、手動でインポートされたアイテムをプレリュードからのものより優先します。 これにより、プレリュードに追加があっても既存のコードは壊れないようになっています。 たとえば、 example という名前のクレートまたはモジュールに pub struct Option; が含まれていたら、 use example::*; とすることで Option は曖昧性なく example に含まれるものを指し示し、 標準ライブラリのものは指しません。

ところが、トレイトをプレリュードに追加すると、捉えがたい形でコードが壊れることがあります。 たとえば、MyTryInto トレイトで定義されている x.try_into() という呼び出しは、 stdTryInto もインポートされているときは、動かなくなる場合があります。 なぜなら、try_into の呼び出しは今や曖昧で、どちらのトレイトから来ているかわからないからです。 だからこそ我々は、 TryInto を未だにプレリュードに追加していませんでした。 追加してしまうと、多くのコードでそのような問題が起こりうるからです。

解決策として、Rust 2021 では新たなプレリュードが使用されます。 変更点は、以下の3つが追加されたということだけです。

追跡用の Issue はこちらです。

移行

Rust 2018 コードベースから Rust 2021 への自動移行の支援のため、2021 エディションには、移行用のリントrust_2021_prelude_collisions が追加されています。

rustfix でコードを Rust 2021 エディションに適合させるためには、次のように実行します。

cargo fix --edition

このリントは、新しくプレリュードに追加されたトレイトで定義されているメソッドと同名の関数やメソッドが呼び出されていることを検知します。 場合によっては、今までと同じ関数が呼び出されるように、あなたのコードを様々な方法で書き換えることもあります。

コードの移行を手作業で行いたい方や rustfix が何を行うかをより詳しく理解したい方のために、どのような状況で移行が必要なのか、逆にどうであれば不要なのを以下に例示していきます。

移行が必要な場合

トレイトメソッドの衝突

あるスコープに、同じメソッド名を持つ2つのトレイトがある場合、どちらのメソッドが使用されるべきかは曖昧です。例えば:

trait MyTrait<A> { // This name is the same as the `from_iter` method on the `FromIterator` trait from `std`. // この関数名は、`std` の `FromIterator` トレイトの `from_iter` メソッドと同名。 fn from_iter(x: Option<A>); } impl<T> MyTrait<()> for Vec<T> { fn from_iter(_: Option<()>) {} } fn main() { // Vec<T> implements both `std::iter::FromIterator` and `MyTrait` // If both traits are in scope (as would be the case in Rust 2021), // then it becomes ambiguous which `from_iter` method to call // Vec<T> は `std::iter::FromIterator` と `MyTrait` の両方を実装する // もし両方のトレイトがスコープに含まれる場合 (Rust 2021 ではそうであるが)、 // どちらの `from_iter` メソッドを呼び出せばいいかが曖昧になる <Vec<i32>>::from_iter(None); }

完全修飾構文を使うと、これを修正できます:

fn main() { // Now it is clear which trait method we're referring to // こうすれば、どちらのトレイトメソッドを指し示しているかが明確になる <Vec<i32> as MyTrait<()>>::from_iter(None); }

dyn Trait オブジェクトの固有メソッド

dyn Trait の値に対してメソッドを呼び出すときに、メソッド名が新しくプレリュードに追加されたトレイトと重複していることがあります:

#![allow(unused)] fn main() { mod submodule { pub trait MyTrait { // This has the same name as `TryInto::try_into` // これは `TryInto::try_into` と同名 fn try_into(&self) -> Result<u32, ()>; } } // `MyTrait` isn't in scope here and can only be referred to through the path `submodule::MyTrait` // `MyTrait` はここではスコープ内になく、パス付きで `submodule::MyTrait` としか利用できない fn bar(f: Box<dyn submodule::MyTrait>) { // If `std::convert::TryInto` is in scope (as would be the case in Rust 2021), // then it becomes ambiguous which `try_into` method to call // `std::convert::TryInto` がスコープ内にあるときは (Rust 2021 ではそうなのだが)、 // どちらの `try_into` メソッドを呼び出せばいいかが曖昧になる f.try_into(); } }

静的ディスパッチのときと違って、トレイトオブジェクトに対してトレイトメソッドを呼び出すときは、そのトレイトがスコープ内にある必要はありません。 TryInto トレイトがスコープ内にあるときは (Rust 2021 ではそうなのですが)、曖昧性が発生します。 MyTrait::try_intostd::convert::TryInto::try_into のどちらが呼び出されるべきなのでしょうか?

この場合、さらなる参照外しをするか、もしくはメソッドレシーバーの型を明示することで修正できます。 これにより、dyn Trait のメソッドとプレリュードのトレイトのメソッドのどちらが選ばれているかが明確になります。 たとえば、上の f.try_into()(&*f).try_into() にすると、try_intodyn Trait に対して呼び出されることがはっきりします。 これに該当するのはMyTrait::try_intoメソッドのみです。

移行が不要な場合

固有メソッド

トレイトメソッドと同名の固有メソッドを定義しているような型もたくさんあります。 たとえば、以下では MyStructfrom_iter を実装していますが、 これは標準ライブラリの FromIterator トレイトのメソッドと同名です。

#![allow(unused)] fn main() { use std::iter::IntoIterator; struct MyStruct { data: Vec<u32> } impl MyStruct { // This has the same name as `std::iter::FromIterator::from_iter` // これは `std::iter::FromIterator::from_iter` と同名 fn from_iter(iter: impl IntoIterator<Item = u32>) -> Self { Self { data: iter.into_iter().collect() } } } impl std::iter::FromIterator<u32> for MyStruct { fn from_iter<I: IntoIterator<Item = u32>>(iter: I) -> Self { Self { data: iter.into_iter().collect() } } } }

固有メソッドは常にトレイトメソッドより優先されるため、移行作業の必要はありません。

実装の参考事項

2021 エディションを導入することで名前解決に衝突が生じるかどうか(すなわち、エディションを変えることでコードが壊れるかどうか)を判断するために、このリントはいくつかの要素を考慮する必要があります。たとえば以下のような点です:

  • 完全修飾呼び出しドット呼び出しメソッド構文のどちらが使われているか?
    • これは、メソッド呼び出し構文の自動参照付けと自動参照外しによる名前の解決方法に影響します。ドット呼び出しメソッド構文では、手動で参照外し/参照付けすることで優先順位を決められますが、完全修飾呼び出しではメソッドパス中に型とトレイト名が指定されていなければなりません (例: <Type as Trait>::method)
  • 固有メソッドトレイトメソッドのどちらが呼び出されているか?
    • 固有メソッドはトレイトメソッドより優先されるので、self を取るトレイトメソッドは、TryInto::try_intoより優先されますが、&self&mut self をとる固有メソッドは、自動参照付けが必要なので優先されません(もっとも、TryIntoself を取るので、それは当てはまりませんが)
  • そのメソッドは corestd から来たものか? (トレイトは自分自身とは衝突しないので)
  • その型は、名前が衝突するようなトレイトを実装しているか?
  • メソッドが動的ディスパッチによって呼び出されているか? (つまり、 self の型が dyn Trait か?)
    • その場合、トレイトのインポートは名前解決に影響しないので、移行リントを出す必要はありません

デフォルトの Cargo のフィーチャリゾルバ

概要

  • edition = "2021" では Cargo.tomlresolver = "2" が設定されているとみなされます。

詳細

Rust 1.51.0 から、Cargo には新しいフィーチャリゾルバがオプトインできるようになっています。 これは、Cargo.tomlresolver = "2" と書くことで有効化できます。

Rust 2021 から、これがデフォルトになりました。 つまり、 Cargo.tomledition = "2021" と書けば、暗黙に resolver = "2" も設定されているとみなされます。

このリゾルバはワークスペース全体に設定され、依存先では無視されます。 また、この設定はワークスペースの最上位のパッケージでしか効きません。 仮想ワークスペースにおいて新しいリゾルバをオプトインしたい場合は、 以前と同様に resolver フィールドを明示的に設定する必要があります。 

新しいフィーチャリゾルバは、クレートへの依存に異なるフィーチャが設定されていてもそれらをマージしないようになりました。 詳細は the announcement of Rust 1.51 に記載されています。

移行

新しいリゾルバに適合させるための自動化された移行ツールはありません。 ほとんどのプロジェクトでは、更新後に必要な変更はあっても微々たるものでしょう。

cargo fix --edition でのアップデート時に、Cargo は新しいリゾルバで依存先のフィーチャに変更があるかどうかを表示します。 たとえば、このように表示されます:

note: Switching to Edition 2021 will enable the use of the version 2 feature resolver in Cargo. This may cause some dependencies to be built with fewer features enabled than previously. More information about the resolver changes may be found at https://doc.rust-lang.org/nightly/edition-guide/rust-2021/default-cargo-resolver.html
When building the following dependencies, the given features will no longer be used:

(訳) 2021 エディションに切り替えると、Cargoのフィーチャリゾルバがバージョン 2 に切り替わります。 切り替え後、いくつかの依存先では有効化されるフィーチャが減少することがあります。 リゾルバの変更点については、 https://doc.rust-lang.org/nightly/edition-guide/rust-2021/default-cargo-resolver.html もご覧ください。
以下の依存先をビルドするときに、以下のフィーチャが使われなくなります:

bstr v0.2.16: default, lazy_static, regex-automata, unicode libz-sys v1.1.3 (as host dependency): libc

これにより、記載されたフィーチャがその依存先で使われずにビルドされるようになることがわかります。

ビルドの失敗

状況によっては、変更後にプロジェクトが正しくビルドされなくなることもあります。 あるパッケージの依存関係が、別のパッケージにおいて特定のフィーチャが有効されることを前提にしている場合、そのフィーチャが使われなくなることでコンパイルに失敗するかもしれません。

たとえば、我々のパッケージにこんな依存関係があったとしましょう:

# Cargo.toml [dependencies] bstr = { version = "0.2.16", default-features = false } # ...

そして依存関係の中にはこんなパッケージもあるとしましょう:

# Another package's Cargo.toml # 別のパッケージの Cargo.toml [build-dependencies] bstr = "0.2.16"

我々のパッケージでは、今までは bstrwords_with_breaks 関数を使用していたとします。この関数は(本来) bstr の "unicode" フィーチャを有効化しないと使えないものです。 歴史的事情から、今まではこれでもうまくいきました。というのも、Cargo は2つのパッケージで使われている bstr のフィーチャを共通化していたからです。 しかしながら、Rust 2021 へのアップデート後、 bstr は1回目(ビルド依存関係として)はデフォルトのフィーチャで、2回目(我々のパッケージの通常の依存先として)はフィーチャなしで、合計2回ビルドされます。 今や bstr は "unicode" フィーチャなしでビルドされるので、 words_with_breaks メソッドは存在せず、メソッドがないというエラーが発生してビルドは失敗します。

ここでの解決策は、依存関係の宣言に我々が実際に使っているフィーチャを書くようにすることです。

[dependencies] bstr = { version = "0.2.16", default-features = false, features = ["unicode"] }

ときには、あなたが直接いじることのできないサードパーティな依存先で問題が発生することもあります。 その場合は、問題が起こっている依存関係について、正しくフィーチャを指定するように、そのプロジェクトにパッチを送るのもよいでしょう。 あるいは、自身の Cargo.toml に記載する依存関係にフィーチャを追加することもできます。 新しいリゾルバには以下のような併合ルールがあり、その下でフィーチャは併合されます。すなわち、

  • 現在ビルドされていないターゲットに対するプラットフォーム特有の依存関係で有効化されているフィーチャは、無視されます。
  • build-dependencies と proc-macro では、通常の依存関係とは独立したフィーチャが使用されます。
  • dev-dependencies では、(tests や examples などの) ターゲットをビルドするときに必要でない限り、フィーチャは有効化されません。

実際の例としては、dieseldiesel_migrations を使用する場合が挙げられます。 これらのパッケージはデータベースへのサポートを提供しますが、データベースはフィーチャを用いて選択されます。たとえば、こんな感じです:

[dependencies] diesel = { version = "1.4.7", features = ["postgres"] } diesel_migrations = "1.4.0"

ここで問題なのは、 diesel_migrations は内部に diesel に依存する手続き的マクロをもちます。 この手続き的マクロは、自身が使用する diesel で有効化されているフィーチャが、依存関係木の他の場所で有効化されているものと同じであると仮定します。 ところが、新しいリゾルバが使用されると、2つの diesel が使用され、そのうち手続き的マクロ用のものは "postgres" フィーチャなしでビルドされるために、ビルドに失敗します。

ここでの解決策は、diesel をビルド時の依存として追加し、そこに必要なフィーチャを指定することです。例えば以下のようになります。

[build-dependencies] diesel = { version = "1.4.7", features = ["postgres"] }

これにより、 Cargo はホスト依存関係(proc-macro と build-dependencies)のフィーチャとして "postgres" を追加します。

訳注:ホスト依存関係とは、コンパイラホスト(コンパイラを実行しているプラットフォーム)向けにビルド・実行される依存を指し、proc-macro クレートや build-dependencies 配下の依存クレートが該当します。 一方、通常の依存関係はコンパイルターゲットのプラットフォーム向けにビルドされます。

これで、 diesel_migrations の手続き的マクロは "postgres" フィーチャが有効化された状態で走り、正しくビルドされます。

(現在開発中の) diesel のリリース 2.0 では、このような仮定なしに動くよう再設計されているため、このような問題は発生しません。

フィーチャを探索する

cargo tree コマンドには、新しいリゾルバへの移行を補助する、素晴らしい新機能が含まれています。 cargo tree を使えば、依存関係木を探索して、どのフィーチャが有効化されているか、そしてなによりなぜそれが有効化されているのかが分かります。

例えば、--duplicates (短縮形: -d) フラグを使用すると、同じパッケージが複数回ビルドされている場所がわかります。 さきほどの bstr を例に取れば、このような表示になるでしょう:

> cargo tree -d bstr v0.2.16 └── foo v0.1.0 (/MyProjects/foo) bstr v0.2.16 [build-dependencies] └── bar v0.1.0 └── foo v0.1.0 (/MyProjects/foo)

この出力から、bstr が複数回ビルドされていることと、どの依存関係をたどると双方が現れるかが分かります。

-f フラグを使えば、それぞれのパッケージがどのフィーチャを使用しているかがわかります。こんな感じです:

cargo tree -f '{p} {f}'

こうすると、Cargo は出力の「フォーマット」を変更して、パッケージと有効化されているフィーチャの双方を表示するようになります。

さらに、-e フラグを使用してどの「辺」を表示してほしいか指定することもできます。 例えば、cargo tree -e features とすれば、各依存関係の間に、各依存関係がどのフィーチャを追加しているのかが表示されます。 -i フラグを使って木を「反転」させると、このオプションはより便利になります。 例えば、依存関係木があまりにも大きくて、何が bstr に依存してるのかよくわからなくても、次のコマンドを実行すればいいです:

> cargo tree -e features -i bstr bstr v0.2.16 ├── bstr feature "default" │ [build-dependencies] │ └── bar v0.1.0 │ └── bar feature "default" │ └── foo v0.1.0 (/MyProjects/foo) ├── bstr feature "lazy_static" │ └── bstr feature "unicode" │ └── bstr feature "default" (*) ├── bstr feature "regex-automata" │ └── bstr feature "unicode" (*) ├── bstr feature "std" │ └── bstr feature "default" (*) └── bstr feature "unicode" (*)

この出力例からは、foobar に "default" フィーチャ付きで依存していることがわかり、 bar はビルド時の依存として bstr に "default" フィーチャ付きで依存していることもわかります。 さらに、bstr の "default" フィーチャによって "unicode" フィーチャ(と、他のフィーチャも)が有効になっていることもわかります。

配列に対する IntoIterator

概要

  • すべてのエディションで、配列が IntoIterator を実装するようになります。
  • Rust 2015 と Rust 2018 では、メソッド呼び出し構文が使われても(つまり array.into_iter() と書いても)、 IntoIterator::into_iter隠されています。 これにより、array.into_iter() は従来どおり (&array).into_iter() に解決されます。
  • Rust 2021 から、array.into_iter()IntoIterator::into_iter を意味するように変更されます。

詳細

Rust 1.53 より前は、配列の参照だけが IntoIterator を実装していました。 すなわち、&[1, 2, 3]&mut [1, 2, 3] に対しては列挙できる一方で、[1, 2, 3] に対して列挙することはできませんでした。

for &e in &[1, 2, 3] {} // Ok :) // OK :) for e in [1, 2, 3] {} // Error :( // エラー :(

これは古くからある Issueですが、見た目ほど解決は簡単ではありません。 トレイト実装を追加するだけでは、既存のコードが壊れてしまいます。 メソッド呼び出し構文の仕組み上、array.into_iter() は現状でも (&array).into_iter() とみなされてコンパイルが通ります。 トレイト実装を追加すると、その意味が変わってしまうのです。

多くのケースで、この手の互換性破壊(トレイト実装の追加)は「軽微」で許容可能とみなされてきました。 しかし、このケースではあまりにも多くのコードが壊れてしまうのです。

何度も提案されてきたのは、「Rust 2021 でのみ配列に IntoIterator を実装する」ことでした。 しかし、これは単に不可能なのです。 エディションは併用されうるので、あるエディションではトレイト実装が存在して、別のエディションでは存在しない、というわけにはいかないからです。

代わりに、(Rust 1.53.0 から)トレイト実装はすべてのエディションで追加されましたが、 Rust 2021 より前のコードが破壊されないようにちょっとしたハックが行われました。 Rust 2015 と 2018 のコードでは、コンパイラは従来どおり array.into_iter()(&array).into_iter() に解決し、あたかもトレイト実装が存在しないかのように振る舞います。 これは .into_iter() というメソッド呼び出し構文だけに適用されます。 一方、このルールは for e in [1, 2, 3], iter.zip([1, 2, 3]), IntoIterator::into_iter([1, 2, 3]) といった他の構文には適用されず、 そのような書き方は全てのエディションで使えるようになります。

互換性破壊を防ぐためにちょっとしたハックが必要になったのは残念ですが、 これによりエディション間の違いが最小限になったのです。

移行

into_iter() への呼び出しのうち、Rust 2021 で意味が変わるようなものに対しては、 array_into_iter というリントが発生します。 1.41 のリリース以降、array_into_iter リントはすでにデフォルトで警告として発出されています(1.55 ではさらにいくつかの機能追加が行われました)。 警告が今現在出ていないコードは、今すぐにでも Rust 2021 に進むことができます!

コードを自動的に Rust 2021 エディションに適合するよう自動移行するか、既に適合するものであることを確認するためには、以下のように実行すればよいです:

cargo fix --edition

エディション間の違いが少ないので、Rust 2021 への移行も非常に簡単です。

配列に対する into_iter のメソッド呼び出しに関しては、(イテレータの)要素が参照でなく所有権を持った値となります。

例えば:

fn main() { let array = [1u8, 2, 3]; for x in array.into_iter() { // x is a `&u8` in Rust 2015 and Rust 2018 // x is a `u8` in Rust 2021 // Rust 2015 と Rust 2018 では、x は `&u8` // Rust 2021 では、x は `u8` } }

移行のための最も簡単な方法は、前のエディションと完全に同じ挙動をするように、 所有権を持った配列上を参照でイテレートするもう一つのメソッド iter() を呼び出すことです:

fn main() { let array = [1u8, 2, 3]; for x in array.iter() { // <- This line changed // この行を書き換えた // x is a `&u8` in all editions // x はすべてのエディションで `&u8` } }

必須でない移行

前のエディションで完全修飾メソッド構文を使っていた場合(例: IntoIterator::into_iter(array))、 これはメソッド呼び出し構文に書き換え可能です(例: array.into_iter())。

クロージャはフィールドごとにキャプチャする

概要

  • || a.x + 1a でなく a.x だけをキャプチャするようになりました。
  • これにより、ドロップのタイミングが変わったり、クロージャが SendClone を実装するかどうかが変わったりします。
    • cargo fix は、このような違いが起こりうると検出した場合、 let _ = &a のような文を挿入して、クロージャが変数全体をキャプチャするように強制します。

詳細

クロージャは、本体の中で使用しているすべてのものを自動的にキャプチャします。 例えば、|| a + 1 と書くと、周囲のコンテキスト中の a への参照が自動的にキャプチャされます。

Rust 2018 以前では、クロージャに使われているのが1つのフィールドだけであっても、クロージャは変数全体をキャプチャします。 例えば、 || a.x + 1a.x への参照だけでなく、a への参照をキャプチャします。 a 全体がキャプチャされると、a の他のフィールドの値を書き換えたりムーブしたりできなくなります。従って以下のようなコードはコンパイルに失敗します:

let a = SomeStruct::new(); drop(a.x); // Move out of one field of the struct // 構造体のフィールドの1つをムーブする println!("{}", a.y); // Ok: Still use another field of the struct // OK: 構造体の他のフィールドは、まだ使える let c = || println!("{}", a.y); // Error: Tries to capture all of `a` // エラー: `a` 全体をキャプチャしようとする c();

Rust 2021 からは、クロージャのキャプチャはより精密になります。 特に、使用されるフィールドだけがキャプチャされるようになります (場合によっては、使用する変数以外にもキャプチャすることもあり得ます。詳細に関しては Rust リファレンスを参照してください)。 したがって、上記のコードは Rust 2021 では問題ありません。

フィールドごとのキャプチャは RFC 2229 の一部として提案されました。この RFC にはより詳しい動機が記載されています。

移行

Rust 2018 のコードベースから Rust 2021 への自動移行の支援のため、2021 エディションには、移行用のリントrust_2021_incompatible_closure_captures が追加されています。

rustfix でコードを Rust 2021 エディションに適合させるためには、次のように実行します。

cargo fix --edition

以下では、クロージャによるキャプチャが出現するコードについて、自動移行が失敗した場合に手動で Rust 2021 に適合するように移行するにはどうすればいいかを考察します。 移行がどのようになされるか知りたい人も以下をお読みください。

クロージャによってキャプチャされる変数が変わると、プログラムの挙動が変わったりコンパイルできなくなったりすることがありますが、その原因は以下の2つです:

  • ドロップの順序や、デストラクタが走るタイミングが変わる場合(詳細
  • クロージャが実装するトレイトが変わる場合(詳細

以下のような状況を検知すると、cargo fix は「ダミーの let」をクロージャの中に挿入して、強制的に全ての変数がキャプチャされるようにします:

#![allow(unused)] fn main() { let x = (vec![22], vec![23]); let c = move || { // "Dummy let" that forces `x` to be captured in its entirety // `x` 全体が強制的にキャプチャされるための「ダミーの let」 let _ = &x; // Otherwise, only `x.0` would be captured here // それがないと、`x.0` だけがここでキャプチャされる println!("{:?}", x.0); }; }

この解析は保守的です。ほとんどの場合、ダミーの let は問題なく消すことができ、消してもプログラムはきちんと動きます。

ワイルドカードパターン

クロージャは本当に読む必要のあるデータだけをキャプチャするようになったので、次のコードは x をキャプチャしません:

#![allow(unused)] fn main() { let x = 10; let c = || { let _ = x; // no-op // 何もしない }; let c = || match x { _ => println!("Hello World!") }; }

この let _ = x は何もしません。 なぜなら、_ パターンは右辺を無視し、さらに、x はメモリ上のある場所(この場合は変数)への参照だからです。

この変更(いくつかの値がキャプチャされなくなること)そのものによってコード変更の提案がなされることはありませんが、後で説明する「ドロップ順序」の変更と組み合わせると、提案がなされる場合もあります。

ちなみに: 似たような式の中には、同じく自動挿入される "ダミーの let" であっても、let _ = &x のように「何もしない」わけではない文もあります。なぜかというと、右辺(&x)はメモリ上のある場所を指し示すのではなく、値が評価されるべき式となるからです(その評価結果は捨てられますが)。

ドロップの順序

クロージャが変数 t の値の所有権を取るとき、その値がドロップされるのは t がスコープ外に出たときではなく、そのクロージャがドロップされたときになります:

#![allow(unused)] fn main() { fn move_value<T>(_: T){} { let t = (vec![0], vec![0]); { let c = || move_value(t); // t is moved here } // c is dropped, which drops the tuple `t` as well // c がドロップされ、そのときにタプル `t` もまたドロップされる } // t goes out of scope here // t はここでスコープを抜ける }

上記のコードの挙動は Rust 2018 と Rust 2021 で同じです。ところが、クロージャが変数の一部の所有権を取るとき、違いが発生します:

#![allow(unused)] fn main() { fn move_value<T>(_: T){} { let t = (vec![0], vec![0]); { let c = || { // In Rust 2018, captures all of `t`. // In Rust 2021, captures only `t.0` // Rust 2018 では、`t` 全体がキャプチャされる。 // Rust 2018 では、`t.0` だけがキャプチャされる move_value(t.0); }; // In Rust 2018, `c` (and `t`) are both dropped when we // exit this block. // Rust 2018 では、 `c` (と `t`) の両方が // このブロックを抜けるときにドロップされる。 // // In Rust 2021, `c` and `t.0` are both dropped when we // exit this block. // Rust 2021 では、 `c` と `t.0` の両方が // このブロックを抜けるときにドロップされる。 } // In Rust 2018, the value from `t` has been moved and is // not dropped. // Rust 2018 では、`t` はすでにムーブされており、ここではドロップされない // // In Rust 2021, the value from `t.0` has been moved, but `t.1` // remains, so it will be dropped here. // Rust 2021 では、`t.0` はムーブされているが、 // `t.1` は残っており、ここでドロップされる } }

ほとんどの場合、ドロップのタイミングが変わってもメモリが解放されるタイミングが変わるだけで、さほど問題にはなりません。 しかし、Drop の実装に副作用のある(いわゆるデストラクタである)場合、ドロップの順序が変わるとプログラムの意味が変わってしまうかもしれません。 その場合は、コンパイラはダミーの let を挿入して変数全体がキャプチャされるように提案します。

トレイト実装

何がキャプチャされているかによって、クロージャには自動的に以下のトレイトが実装されます:

Rust 2021 では、キャプチャされる値が変わることによって、クロージャが実装するトレイトも変わることがあります。 先ほどの移行リントは、それぞれのクロージャについて、これまで実装されていた自動トレイトが何であるか、そして移行後もそれらが残るかどうかを調べます。 もし今まで実装されていたトレイトが実装されなくなる場合、「ダミーの let」が挿入されます。

例えば、スレッド間で生ポインタを受け渡しする一般的な方法に、ポインタを構造体でラップし、そのラッパー構造体に自動トレイト Send/Sync を実装するというものがあります。 thread::spawn に渡されるクロージャが使うのは、ラッパー構造体のうち特定の変数だけですが、キャプチャされるのはラッパー構造体全体です。 ラッパー構造体は Send/Sync なので、コードは安全であるとみなされ、コンパイルは成功します。

フィールドごとのキャプチャが導入されると、キャプチャ内で使用されているフィールドだけがキャプチャされますが、フィールドの中身はもともと Send/Sync でなかったのですから、せっかくラッパーを作っても元の木阿弥です。

#![allow(unused)] fn main() { use std::thread; struct Ptr(*mut i32); unsafe impl Send for Ptr {} let mut x = 5; let px = Ptr(&mut x as *mut i32); let c = thread::spawn(move || { unsafe { *(px.0) += 10; } }); // Closure captured px.0 which is not Send // クロージャは px.0 をキャプチャしたが、これは Send ではない }

panic マクロの一貫性

概要

  • panic!(..) では常に format_args!(..) が使われるようになりました。つまり、println!() と同じ書き方をすることになります。
  • panic!("{") と書くことはできなくなりました。{{{ とエスケープしなくてはなりません。
  • x が文字列リテラルでないときに、 panic!(x) と書くことはできなくなりました。
    • 文字列でないペイロード付きでパニックしたい場合、 std::panic::panic_any(x) を使うようにしてください。
    • もしくは、xDisplay 実装を用いて、panic!("{}", x) と書いてください。
  • assert!(expr, ..) に関しても同様です。

詳細

panic!() は、Rust で最もよく知られたマクロの一つです。 しかし、このマクロにはいくぶん非直感的な挙動がありましたが、 今までは後方互換性の問題から修正できませんでした。

// Rust 2018 panic!("{}", 1); // Ok, panics with the message "1" // OK。 "1" というメッセージと共にパニックする panic!("{}"); // Ok, panics with the message "{}" // OK。 "{}" というメッセージと共にパニックする

panic!() マクロは、2つ以上の引数が渡されたときだけ、フォーマット文字列を使用します。 引数が1つのときは、引数の中身に見向きもしません。

// Rust 2018 let a = "{"; println!(a); // Error: First argument must be a format string literal // エラー: 第一引数は文字列リテラルでなくてはならない panic!(a); // Ok: The panic macro doesn't care // OK: panicマクロは気にしない

その上、このマクロは panic!(123) のように文字列でない引数を渡すこともできました。 このような使い方は稀で、ほとんど役に立ちません。 というのも、この呼び出しで出力されるメッセージはあきれるほど役に立たないからです: panicked at 'Box<Any>' (訳: 'Box<Any> でパニックしました)。

これで特に困るのは、暗黙のフォーマット引数が安定化されたときです。 この機能により、println!("hello {}", name) の代わりに println!("hello {name}") と略記できるようになります。 しかし、panic!("hello {name}") は期待される挙動を示しません。 なぜなら、panic!() は引数が1つだけ与えられたときにそれをフォーマット文字列として扱わないからです。

この紛らわしい状況を解決するために、Rust 2021 の panic!() マクロはより一貫したものになりました。 新しい panic!() マクロは、単一引数として任意の式を受け付けることがなくなりました。 代わりに、println!() と同様に、常に最初の引数をフォーマット文字列として処理するようになりました。 panic!() マクロが任意のペイロードを受け付けるわけではなくなったので、 フォーマット文字列以外のペイロードと共にパニックさせる唯一の方法は、 panic_any()を使うことだけになりました。

// Rust 2021 panic!("{}", 1); // Ok, panics with the message "1" // Ok。"1" というメッセージと共にパニックする panic!("{}"); // Error, missing argument // エラー。引数が足りない panic!(a); // Error, must be a string literal // エラー。文字列リテラルでないといけない

さらに、core::panic!()std::panic!() は Rust 2021 で同一のものになります。 現在は、これらの間には歴史的な違いがあり、 #![no_std] のオンオフを切り替えることで見て取ることができます。

移行

panic への呼び出しのうち、非推奨の挙動を使用していて Rust 2021 ではエラーになる場所に対して、non_fmt_panics というリントが発生します。 Rust 1.50 以降、non_fmt_panics リントはすでにデフォルトで警告として発出されています(後のバージョンではさらにいくつかの機能追加が行われました)。 警告が今現在出ていないコードは、今すぐにでも Rust 2021 に進むことができます!

コードを自動的に Rust 2021 エディションに適合するよう自動移行するか、既に適合するものであることを確認するためには、以下のように実行すればよいです:

cargo fix --edition

手動で移行することを選んだり、そうする必要がある場合には、各 panic の呼び出しについて、 println と同様のフォーマットに書き換えるか、std::panic::panic_any を用いて非文字列型のデータと共にパニックさせるかを選ぶ必要があります。

例えば、panic!(MyStruct) の場合、std::panic::panic_any (これはマクロでなく関数であることに注意)を使うよう書き換えて、std::panic::panic_any(MyStruct) とすればよいです。

パニックのメッセージに波括弧が含まれているのに、引数の個数が一致しない場合は(例: panic!("Some curlies: {}"))、 println! と同じ構文を使うか(つまり panic!("{}", "Some curlies: {}") とするか)、波括弧をエスケープすれば(つまり panic!("Some curlies: {{}}") とすれば)、 その文字列リテラルを用いてパニックすることができます。

構文の予約

概要

  • shikibetsushi#, shikibetsushi"...", shikibetsushi'...' の3つの構文が新たに予約され、トークン分割されなくなりました。
  • 主に影響を受けるのはマクロです。例えば、quote!{ #a#b } と書くことはできなくなりました。
  • キーワードが特別扱いされることもないので、例えば match"..." {} と書くこともできなくなりました。
  • 識別子と後続の #, ", ' の間に空白文字を挿入することで、エラーを回避できます。
  • エディション移行ツールは、必要な場所に空白を挿入してくれます。

詳細

私達は、将来新しい構文を導入する余地を残すため、接頭辞付きの識別子とリテラルの構文を予約することにしました。 予約されたのは、任意の識別子 prefix を用いて prefix#identifier, prefix"string", prefix'c', prefix#123 のいずれかの形で書かれるものです。 (ただし、b'...'(バイト文字列)やr"..."(生文字列)のように、すでに意味が割り当てられているものを除きます。)

これにより、将来エディションをまたくごとなく構文を拡張できるようになります。 これを、次のエディションまでの一時的な構文のために使ったり、もし適切なら、恒久的な構文のために使ったりするでしょう。

エディションの区切りがないと、これは破壊的変更に当たります。 なぜなら、現在のマクロは、例えば hello"world" という構文を、 hello"world" の2つのトークンとして受け入れるからです。 もっとも、(自動)修正はシンプルで、hello "world" のように空白を入れるだけです。 同様に、prefix#identprefix #ident とする必要があります。 エディション移行ツールは、そのように修正してくれます。

これが提案された RFC は、このような書き方をトークン分割エラーにすると決めているだけで、 特定の接頭辞に意味を持たせることはまだしていません。 接頭辞に何らかの役割を割り当てるのは、将来の提案にその余地が残されています。 接頭辞が予約されたおかげで、今後は破壊的変更なく新しい構文を導入できます。

例えば、以下のような接頭辞が使えるようになるかもしれません(ただし、いずれもまだ提案が固まったわけではありません):

  • k#keyword で、現在のエディションにまだ導入されていないキーワードを書けるようにする。 たとえば、2015 エディションでは async はキーワードではありませんが、 このような接頭辞が使えたのならば、2018 エディションで async が予約語になるのを待たずに、2015 エディションでも k#async が使えたということになります。
  • f"" で、フォーマット文字列の略記とする。 例えば、f"hello {name}" と書いたら、それと等価な format!() の呼び出しと見なす。
  • s""String リテラルを表す。
  • c""z"" で、ヌル終端のC言語の文字列を表す。

移行

Rust 2018 のコードベースから Rust 2021 への自動移行の支援のため、2021 エディションには、移行用のリントrust_2021_prefixes_incompatible_syntax が追加されています。

rustfix でコードを Rust 2021 エディションに適合させるためには、次のように実行します。

cargo fix --edition

コード移行を手で行いたいか、行う必要があっても、移行は非常に簡単です。

例えば、次のように定義されたマクロがあったとしましょう:

#![allow(unused)] fn main() { macro_rules! my_macro { ($a:tt $b:tt) => {}; } }

Rust 2015 と 2018 では、以下のように、1つ目と2つ目のトークンの間に空白を入れることなくマクロを呼び出しても問題ありませんでした:

my_macro!(z"hey");

Rust 2021 では z という接頭辞は許されないので、このマクロを呼び出すためには、以下のように接頭辞の後にスペースを入れる必要があります:

my_macro!(z "hey");

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

Raw lifetimes

Summary

  • 'r#ident_or_keyword is now allowed as a lifetime, which allows using keywords such as 'r#fn.

Details

Raw lifetimes are introduced in the 2021 edition to support the ability to migrate to newer editions that introduce new keywords. This is analogous to raw identifiers which provide the same functionality for identifiers. For example, the 2024 edition introduced the gen keyword. Since lifetimes cannot be keywords, this would cause code that use a lifetime 'gen to fail to compile. Raw lifetimes allow the migration lint to modify those lifetimes to 'r#gen which do allow keywords.

In editions prior to 2021, raw lifetimes are parsed as separate tokens. For example 'r#foo is parsed as three tokens: 'r, #, and foo.

Migration

As a part of the 2021 edition a migration lint, rust_2021_prefixes_incompatible_syntax, has been added in order to aid in automatic migration of Rust 2018 codebases to Rust 2021.

In order to migrate your code to be Rust 2021 Edition compatible, run:

cargo fix --edition

Should you want or need to manually migrate your code, migration is fairly straight-forward.

Let's say you have a macro that is defined like so:

#![allow(unused)] fn main() { macro_rules! my_macro { ($a:tt $b:tt $c:tt) => {}; } }

In Rust 2015 and 2018 it's legal for this macro to be called like so with no space between the tokens:

my_macro!('r#foo);

In the 2021 edition, this is now parsed as a single token. In order to call this macro, you must add a space before the identifier like so:

my_macro!('r# foo);

警告からエラーへの格上げ

概要

  • bare_trait_objects リントか ellipsis_inclusive_range_patterns リントが出るコードは、Rust 2021 ではエラーになります。

詳細

現存する2つのリントが Rust 2021 ではエラーになります。古いエディションではこれらは警告のままです。

bare_trait_objects:

Rust 2021 では、トレイトオブジェクトを表すために、dyn キーワードを使用することが必須になりました。

例えば、以下のコードでは &MyTraitdyn キーワードが含まれていないため、Rust 2021 ではただのリントではなくエラーが発生します:

#![allow(unused)] fn main() { pub trait MyTrait {} pub fn my_function(_trait_object: &MyTrait) { // should be `&dyn MyTrait` // `&dyn MyTrait` と書かなくてはならない unimplemented!() } }

ellipsis_inclusive_range_patterns:

閉区間パターン(つまり、終端の値を含む範囲)を表す、非推奨の ... 構文は、Rust 2021 では使えなくなります。 式との統一性のため、..= を使うことが推奨されていました。

例えば、次のコードはパターンとして ... を使っているため、Rust 2021 ではただのリントではなくエラーが発生します:

#![allow(unused)] fn main() { pub fn less_or_eq_to_100(n: u8) -> bool { matches!(n, 0...100) // should be `0..=100` // `0..=100` と書かなくてはならない } }

移行

あなたの Rust 2015 か 2018 のコードで、bare_trait_objectsellipsis_inclusive_range_patterns といったエラーが出ず、#![allow()] などを使ってこれらのリントを許可する設定をしていなければ、移行の際にやることはありません。

... をパターンに使用していたり、トレイトオブジェクトに dyn を使っていないクレートがある場合は、 cargo fix --edition を実行すればよいです。

マクロ規則における OR パターン

概要

  • macro_rules におけるパターンの挙動がほんの少し変更されました:
    • macro_rules において、$_:pat| を使ったパターンにもマッチするようになりました。例えば、A | B にマッチします。
    • 新しく導入された $_:pat_param は、かつての $_:pat と同じ挙動を再現します。すなわち、こちらは(トップレベルの)| にはマッチしません。
    • $_:pat_param は全てのエディションで使用可能です。

詳細

Rust 1.53.0 から、パターン中のどこでも、| をネストして使えるようになりました。 これにより、Some(1) | Some(2) でなく Some(1 | 2) と書くことができるようになりました。 今まではこうは書けなかったので、これは破壊的変更ではありません。

ところが、この変更は macro_rules で定義されたマクロ にも影響します。 macro_rules では、:pat というフラグメント指定子で、パターンを受け付けることができます。 現在のところ、:pat はトップレベルの | にマッチしません。 なぜなら Rust 1.53 以前は、全てのパターンが(どのネストレベルにでも)| を含むことができるわけではなかったからです。 matches!() のように、 A | B のようなパターンを受け付けるマクロを書くには、 $($_:pat)|+ のような書き方をしなくてはなりませんでした。

既存のマクロを壊す可能性があるため、Rust 1.53.0 では :pat| を含むことができるようには変更されませんでした。 代わりに、Rust 2021 で変更がなされました。 新しいエディションでは、:pat フラグメント指定子は A | B にマッチします

Rust 2021 では、$_:pat フラグメントに | そのものを続けることはできません。 パターンフラグメントに | が続いてるものにマッチさせたいような場合は、新しく追加された :pat_param が過去と同じ挙動を示すようになっています。

ただし、エディションはクレートごとに設定されることに注意してください。 つまり、マクロが定義されているクレートのエディションだけが関係します。 マクロを使用する方のクレートのエディションは、マクロの挙動に影響しません。

移行

$_:pat が使われている場所のうち、Rust 2021 で意味が変わるようなものに対しては、rust_2021_incompatible_or_patterns というリントが発生します。

コードを自動的に Rust 2021 エディションに適合するよう自動移行するか、既に適合するものであることを確認するためには、以下のように実行すればよいです:

cargo fix --edition

あなたのマクロが、$_:pat がトップレベルの | にマッチしないという挙動に依存している場合は、 $_:pat$_:pat_param に書き換える必要があります。

例えば以下のようになります。

#![allow(unused)] fn main() { macro_rules! my_macro { ($x:pat | $y:pat) => { // TODO: implementation // TODO: 実装 } } // This macro works in Rust 2018 since `$x:pat` does not match against `|`: // Rust 2018 では、`$x:pat` が `|` にマッチしないので、以下のマクロは正常に動きます: my_macro!(1 | 2); // In Rust 2021 however, the `$_:pat` fragment matches `|` and is not allowed // to be followed by a `|`. To make sure this macro still works in Rust 2021 // change the macro to the following: // 一方 Rust 2021 では、`$_:pat` フラグメントは `|` にもマッチし、 // `|` が続くのは許されなくなりました。 // Rust 2021 でもマクロが動作するためには、マクロを以下のように変更しなくてはなりません: macro_rules! my_macro { ($x:pat_param | $y:pat) => { // <- this line is different // この行を変えた // TODO: implementation // TODO: 実装 } } }

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

C-string literals

Summary

  • Literals of the form c"foo" or cr"foo" represent a string of type &core::ffi::CStr.

Details

Starting with Rust 1.77, C-strings can be written using C-string literal syntax with the c or cr prefix.

Previously, it was challenging to properly produce a valid string literal that could interoperate with C APIs which terminate with a NUL byte. The cstr crate was a popular solution, but that required compiling a proc-macro which was quite expensive. Now, C-strings can be written directly using literal syntax notation, which will generate a value of type &core::ffi::CStr which is automatically terminated with a NUL byte.

#![allow(unused)] fn main() { use core::ffi::CStr; assert_eq!(c"hello", CStr::from_bytes_with_nul(b"hello\0").unwrap()); assert_eq!( c"byte escapes \xff work", CStr::from_bytes_with_nul(b"byte escapes \xff work\0").unwrap() ); assert_eq!( c"unicode escapes \u{00E6} work", CStr::from_bytes_with_nul(b"unicode escapes \xc3\xa6 work\0").unwrap() ); assert_eq!( c"unicode characters αβγ encoded as UTF-8", CStr::from_bytes_with_nul( b"unicode characters \xce\xb1\xce\xb2\xce\xb3 encoded as UTF-8\0" ) .unwrap() ); assert_eq!( c"strings can continue \ on multiple lines", CStr::from_bytes_with_nul(b"strings can continue on multiple lines\0").unwrap() ); }

C-strings do not allow interior NUL bytes (such as with a \0 escape).

Similar to regular strings, C-strings also support "raw" syntax with the cr prefix. These raw C-strings do not process backslash escapes which can make it easier to write strings that contain backslashes. Double-quotes can be included by surrounding the quotes with the # character. Multiple # characters can be used to avoid ambiguity with internal "# sequences.

#![allow(unused)] fn main() { assert_eq!(cr"foo", c"foo"); // Number signs can be used to embed interior double quotes. assert_eq!(cr#""foo""#, c"\"foo\""); // This requires two #. assert_eq!(cr##""foo"#"##, c"\"foo\"#"); // Escapes are not processed. assert_eq!(cr"C:\foo", c"C:\\foo"); }

See The Reference for more details.

Migration

Migration is only necessary for macros which may have been assuming a sequence of tokens that looks similar to c"…" or cr"…", which previous to the 2021 edition would tokenize as two separate tokens, but in 2021 appears as a single token.

As part of the syntax reservation for the 2021 edition, any macro input which may run into this issue should issue a warning from the rust_2021_prefixes_incompatible_syntax migration lint. See that chapter for more detail.

Rust 2024

情報
RFC#3501
リリースバージョン1.85.0

言語

以降の節では、2024 エディションでの言語へのアップデートについて詳説します。

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

RPIT lifetime capture rules

This chapter describes changes related to the Lifetime Capture Rules 2024 introduced in RFC 3498, including how to use opaque type precise capturing (introduced in RFC 3617) to migrate your code.

Summary

  • In Rust 2024, all in-scope generic parameters, including lifetime parameters, are implicitly captured when the use<..> bound is not present.
  • Uses of the Captures trick (Captures<..> bounds) and of the outlives trick (e.g. '_ bounds) can be replaced by use<..> bounds (in all editions) or removed entirely (in Rust 2024).

Details

Capturing

Capturing a generic parameter in an RPIT (return-position impl Trait) opaque type allows for that parameter to be used in the corresponding hidden type. In Rust 1.82, we added use<..> bounds that allow specifying explicitly which generic parameters to capture. Those will be helpful for migrating your code to Rust 2024, and will be helpful in this chapter for explaining how the edition-specific implicit capturing rules work. These use<..> bounds look like this:

#![allow(unused)] fn main() { #![feature(precise_capturing)] fn capture<'a, T>(x: &'a (), y: T) -> impl Sized + use<'a, T> { // ~~~~~~~~~~~~~~~~~~~~~~~ // This is the RPIT opaque type. // // It captures `'a` and `T`. (x, y) //~~~~~~ // The hidden type is: `(&'a (), T)`. // // This type can use `'a` and `T` because they were captured. } }

The generic parameters that are captured affect how the opaque type can be used. E.g., this is an error because the lifetime is captured despite the fact that the hidden type does not use the lifetime:

#![allow(unused)] fn main() { #![feature(precise_capturing)] fn capture<'a>(_: &'a ()) -> impl Sized + use<'a> {} fn test<'a>(x: &'a ()) -> impl Sized + 'static { capture(x) //~^ ERROR lifetime may not live long enough } }

Conversely, this is OK:

#![allow(unused)] fn main() { #![feature(precise_capturing)] fn capture<'a>(_: &'a ()) -> impl Sized + use<> {} fn test<'a>(x: &'a ()) -> impl Sized + 'static { capture(x) //~ OK } }

Edition-specific rules when no use<..> bound is present

If the use<..> bound is not present, then the compiler uses edition-specific rules to decide which in-scope generic parameters to capture implicitly.

In all editions, all in-scope type and const generic parameters are captured implicitly when the use<..> bound is not present. E.g.:

#![allow(unused)] fn main() { #![feature(precise_capturing)] fn f_implicit<T, const C: usize>() -> impl Sized {} // ~~~~~~~~~~ // No `use<..>` bound is present here. // // In all editions, the above is equivalent to: fn f_explicit<T, const C: usize>() -> impl Sized + use<T, C> {} }

In Rust 2021 and earlier editions, when the use<..> bound is not present, generic lifetime parameters are only captured when they appear syntactically within a bound in RPIT opaque types in the signature of bare functions and associated functions and methods within inherent impls. However, starting in Rust 2024, these in-scope generic lifetime parameters are unconditionally captured. E.g.:

#![allow(unused)] fn main() { #![feature(precise_capturing)] fn f_implicit(_: &()) -> impl Sized {} // In Rust 2021 and earlier, the above is equivalent to: fn f_2021(_: &()) -> impl Sized + use<> {} // In Rust 2024 and later, it's equivalent to: fn f_2024(_: &()) -> impl Sized + use<'_> {} }

This makes the behavior consistent with RPIT opaque types in the signature of associated functions and methods within trait impls, uses of RPIT within trait definitions (RPITIT), and opaque Future types created by async fn, all of which implicitly capture all in-scope generic lifetime parameters in all editions when the use<..> bound is not present.

Outer generic parameters

Generic parameters from an outer impl are considered to be in scope when deciding what is implicitly captured. E.g.:

#![allow(unused)] fn main() { #![feature(precise_capturing)] struct S<T, const C: usize>((T, [(); C])); impl<T, const C: usize> S<T, C> { // ~~~~~~~~~~~~~~~~~ // These generic parameters are in scope. fn f_implicit<U>() -> impl Sized {} // ~ ~~~~~~~~~~ // ^ This generic is in scope too. // ^ // | // No `use<..>` bound is present here. // // In all editions, it's equivalent to: fn f_explicit<U>() -> impl Sized + use<T, U, C> {} } }

Lifetimes from higher-ranked binders

Similarly, generic lifetime parameters introduced into scope by a higher-ranked for<..> binder are considered to be in scope. E.g.:

#![allow(unused)] fn main() { #![feature(precise_capturing)] trait Tr<'a> { type Ty; } impl Tr<'_> for () { type Ty = (); } fn f_implicit() -> impl for<'a> Tr<'a, Ty = impl Copy> {} // In Rust 2021 and earlier, the above is equivalent to: fn f_2021() -> impl for<'a> Tr<'a, Ty = impl Copy + use<>> {} // In Rust 2024 and later, it's equivalent to: //fn f_2024() -> impl for<'a> Tr<'a, Ty = impl Copy + use<'a>> {} // ~~~~~~~~~~~~~~~~~~~~ // However, note that the capturing of higher-ranked lifetimes in // nested opaque types is not yet supported. }

Argument position impl Trait (APIT)

Anonymous (i.e. unnamed) generic parameters created by the use of APIT (argument position impl Trait) are considered to be in scope. E.g.:

#![allow(unused)] fn main() { #![feature(precise_capturing)] fn f_implicit(_: impl Sized) -> impl Sized {} // ~~~~~~~~~~ // This is called APIT. // // The above is *roughly* equivalent to: fn f_explicit<_0: Sized>(_: _0) -> impl Sized + use<_0> {} }

Note that the former is not exactly equivalent to the latter because, by naming the generic parameter, turbofish syntax can now be used to provide an argument for it. There is no way to explicitly include an anonymous generic parameter in a use<..> bound other than by converting it to a named generic parameter.

Migration

Migrating while avoiding overcapturing

The impl_trait_overcaptures lint flags RPIT opaque types that will capture additional lifetimes in Rust 2024. This lint is part of the rust-2024-compatibility lint group which is automatically applied when running cargo fix --edition. In most cases, the lint can automatically insert use<..> bounds where needed such that no additional lifetimes are captured in Rust 2024.

To migrate your code to be compatible with Rust 2024, run:

cargo fix --edition

For example, this will change:

#![allow(unused)] fn main() { fn f<'a>(x: &'a ()) -> impl Sized { *x } }

...into:

#![allow(unused)] fn main() { #![feature(precise_capturing)] fn f<'a>(x: &'a ()) -> impl Sized + use<> { *x } }

Without this use<> bound, in Rust 2024, the opaque type would capture the 'a lifetime parameter. By adding this bound, the migration lint preserves the existing semantics.

Migrating cases involving APIT

In some cases, the lint cannot make the change automatically because a generic parameter needs to be given a name so that it can appear within a use<..> bound. In these cases, the lint will alert you that a change may need to be made manually. E.g., given:

#![allow(unused)] fn main() { fn f<'a>(x: &'a (), y: impl Sized) -> impl Sized { (*x, y) } // ^^ ~~~~~~~~~~ // This is a use of APIT. // //~^ WARN `impl Sized` will capture more lifetimes than possibly intended in edition 2024 //~| NOTE specifically, this lifetime is in scope but not mentioned in the type's bounds fn test<'a>(x: &'a (), y: ()) -> impl Sized + 'static { f(x, y) } }

The code cannot be converted automatically because of the use of APIT and the fact that the generic type parameter must be named in the use<..> bound. To convert this code to Rust 2024 without capturing the lifetime, you must name that type parameter. E.g.:

#![allow(unused)] fn main() { #![feature(precise_capturing)] #![deny(impl_trait_overcaptures)] fn f<'a, T: Sized>(x: &'a (), y: T) -> impl Sized + use<T> { (*x, y) } // ~~~~~~~~ // The type parameter has been named here. fn test<'a>(x: &'a (), y: ()) -> impl Sized + use<> { f(x, y) } }

Note that this changes the API of the function slightly as a type argument can now be explicitly provided for this parameter using turbofish syntax. If this is undesired, you might consider instead whether you can simply continue to omit the use<..> bound and allow the lifetime to be captured. This might be particularly desirable if you might in the future want to use that lifetime in the hidden type and would like to save space for that.

Migrating away from the Captures trick

Prior to the introduction of precise capturing use<..> bounds in Rust 1.82, correctly capturing a lifetime in an RPIT opaque type often required using the Captures trick. E.g.:

#![allow(unused)] fn main() { #[doc(hidden)] pub trait Captures<T: ?Sized> {} impl<T: ?Sized, U: ?Sized> Captures<T> for U {} fn f<'a, T>(x: &'a (), y: T) -> impl Sized + Captures<(&'a (), T)> { // ~~~~~~~~~~~~~~~~~~~~~ // This is called the `Captures` trick. (x, y) } fn test<'t, 'x>(t: &'t (), x: &'x ()) { f(t, x); } }

With the use<..> bound syntax, the Captures trick is no longer needed and can be replaced with the following in all editions:

#![allow(unused)] fn main() { #![feature(precise_capturing)] fn f<'a, T>(x: &'a (), y: T) -> impl Sized + use<'a, T> { (x, y) } fn test<'t, 'x>(t: &'t (), x: &'x ()) { f(t, x); } }

In Rust 2024, the use<..> bound can often be omitted entirely, and the above can be written simply as:

#![allow(unused)] fn main() { #![feature(lifetime_capture_rules_2024)] fn f<'a, T>(x: &'a (), y: T) -> impl Sized { (x, y) } fn test<'t, 'x>(t: &'t (), x: &'x ()) { f(t, x); } }

There is no automatic migration for this, and the Captures trick still works in Rust 2024, but you might want to consider migrating code manually away from using this old trick.

Migrating away from the outlives trick

Prior to the introduction of precise capturing use<..> bounds in Rust 1.82, it was common to use the "outlives trick" when a lifetime needed to be used in the hidden type of some opaque. E.g.:

#![allow(unused)] fn main() { fn f<'a, T: 'a>(x: &'a (), y: T) -> impl Sized + 'a { // ~~~~ ~~~~ // ^ This is the outlives trick. // | // This bound is needed only for the trick. (x, y) // ~~~~~~ // The hidden type is `(&'a (), T)`. } }

This trick was less baroque than the Captures trick, but also less correct. As we can see in the example above, even though any lifetime components within T are independent from the lifetime 'a, we're required to add a T: 'a bound in order to make the trick work. This created undue and surprising restrictions on callers.

Using precise capturing, you can write the above instead, in all editions, as:

#![allow(unused)] fn main() { #![feature(precise_capturing)] fn f<T>(x: &(), y: T) -> impl Sized + use<'_, T> { (x, y) } fn test<'t, 'x>(t: &'t (), x: &'x ()) { f(t, x); } }

In Rust 2024, the use<..> bound can often be omitted entirely, and the above can be written simply as:

#![allow(unused)] fn main() { #![feature(precise_capturing)] #![feature(lifetime_capture_rules_2024)] fn f<T>(x: &(), y: T) -> impl Sized { (x, y) } fn test<'t, 'x>(t: &'t (), x: &'x ()) { f(t, x); } }

There is no automatic migration for this, and the outlives trick still works in Rust 2024, but you might want to consider migrating code manually away from using this old trick.

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

if let temporary scope

Summary

  • In an if let $pat = $expr { .. } else { .. } expression, the temporary values generated from evaluating $expr will be dropped before the program enters the else branch instead of after.

Details

The 2024 Edition changes the drop scope of temporary values in the scrutinee1 of an if let expression. This is intended to help reduce the potentially unexpected behavior involved with the temporary living for too long.

Before 2024, the temporaries could be extended beyond the if let expression itself. For example:

#![allow(unused)] fn main() { // Before 2024 use std::sync::RwLock; fn f(value: &RwLock<Option<bool>>) { if let Some(x) = *value.read().unwrap() { println!("value is {x}"); } else { let mut v = value.write().unwrap(); if v.is_none() { *v = Some(true); } } // <--- Read lock is dropped here in 2021 } }

In this example, the temporary read lock generated by the call to value.read() will not be dropped until after the if let expression (that is, after the else block). In the case where the else block is executed, this causes a deadlock when it attempts to acquire a write lock.

The 2024 Edition shortens the lifetime of the temporaries to the point where the then-block is completely evaluated or the program control enters the else block.

#![allow(unused)] fn main() { // Starting with 2024 use std::sync::RwLock; fn f(value: &RwLock<Option<bool>>) { if let Some(x) = *value.read().unwrap() { println!("value is {x}"); } // <--- Read lock is dropped here in 2024 else { let mut v = value.write().unwrap(); if v.is_none() { *v = Some(true); } } } }

See the temporary scope rules for more information about how temporary scopes are extended. See the tail expression temporary scope chapter for a similar change made to tail expressions.

1

The scrutinee is the expression being matched on in the if let expression.

Migration

It is always safe to rewrite if let with a match. The temporaries of the match scrutinee are extended past the end of the match expression (typically to the end of the statement), which is the same as the 2021 behavior of if let.

The if_let_rescope lint suggests a fix when a lifetime issue arises due to this change or the lint detects that a temporary value with a custom, non-trivial Drop destructor is generated from the scrutinee of the if let. For instance, the earlier example may be rewritten into the following when the suggestion from cargo fix is accepted:

#![allow(unused)] fn main() { use std::sync::RwLock; fn f(value: &RwLock<Option<bool>>) { match *value.read().unwrap() { Some(x) => { println!("value is {x}"); } _ => { let mut s = value.write().unwrap(); if s.is_none() { *s = Some(true); } } } // <--- Read lock is dropped here in both 2021 and 2024 } }

In this particular example, that's probably not what you want due to the aforementioned deadlock! However, some scenarios may be assuming that the temporaries are held past the else clause, in which case you may want to retain the old behavior.

The if_let_rescope lint is part of the rust-2024-compatibility lint group which is included in the automatic edition migration. In order to migrate your code to be Rust 2024 Edition compatible, run:

cargo fix --edition

After the migration, it is recommended that you review all of the changes of if let to match and decide what is the behavior that you need with respect to when temporaries are dropped. If you determine that the change is unnecessary, then you can revert the change back to if let.

If you want to manually inspect these warnings without performing the edition migration, you can enable the lint with:

#![allow(unused)] fn main() { // Add this to the root of your crate to do a manual migration. #![warn(if_let_rescope)] }

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

Tail expression temporary scope

Summary

  • Temporary values generated in evaluation of the tail expression of a function or closure body, or a block may now be dropped before local variables, and are sometimes not extended to the next larger temporary scope.

Details

The 2024 Edition changes the drop order of temporary values in tail expressions. It often comes as a surprise that, before the 2024 Edition, temporary values in tail expressions can live longer than the block itself, and are dropped later than the local variable bindings, as in the following example:

#![allow(unused)] fn main() { // Before 2024 use std::cell::RefCell; fn f() -> usize { let c = RefCell::new(".."); c.borrow().len() // error[E0597]: `c` does not live long enough } }

This yields the following error with the 2021 Edition:

error[E0597]: `c` does not live long enough --> src/lib.rs:4:5 | 3 | let c = RefCell::new(".."); | - binding `c` declared here 4 | c.borrow().len() // error[E0597]: `c` does not live long enough | ^--------- | | | borrowed value does not live long enough | a temporary with access to the borrow is created here ... 5 | } | - | | | `c` dropped here while still borrowed | ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Ref<'_, &str>` | = note: the temporary is part of an expression at the end of a block; consider forcing this temporary to be dropped sooner, before the block's local variables are dropped help: for example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block | 4 | let x = c.borrow().len(); x // error[E0597]: `c` does not live long enough | +++++++ +++ For more information about this error, try `rustc --explain E0597`.

In 2021 the local variable c is dropped before the temporary created by c.borrow(). The 2024 Edition changes this so that the temporary value c.borrow() is dropped first, followed by dropping the local variable c, allowing the code to compile as expected.

Temporary scope may be narrowed

When a temporary is created in order to evaluate an expression, the temporary is dropped based on the temporary scope rules. Those rules define how long the temporary will be kept alive. Before 2024, temporaries from tail expressions of a block would be extended outside of the block to the next temporary scope boundary. In many cases this would be the end of a statement or function body. In 2024, the temporaries of the tail expression may now be dropped immediately at the end of the block (before any local variables in the block).

This narrowing of the temporary scope may cause programs to fail to compile in 2024. For example:

// This example works in 2021, but fails to compile in 2024. fn main() { let x = { &String::from("1234") }.len(); }

In this example, in 2021, the temporary String is extended outside of the block, past the call to len(), and is dropped at the end of the statement. In 2024, it is dropped immediately at the end of the block, causing a compile error about the temporary being dropped while borrowed.

The solution for these kinds of situations is to lift the block expression out to a local variable so that the temporary lives long enough:

fn main() { let s = { &String::from("1234") }; let x = s.len(); }

This particular example takes advantage of temporary lifetime extension. Temporary lifetime extension is a set of specific rules which allow temporaries to live longer than they normally would. Because the String temporary is behind a reference, the String temporary is extended long enough for the next statement to call len() on it.

See the if let temporary scope chapter for a similar change made to temporary scopes of if let expressions.

Migration

Unfortunately, there are no semantics-preserving rewrites to shorten the lifetime for temporary values in tail expressions1. The tail_expr_drop_order lint detects if a temporary value with a custom, non-trivial Drop destructor is generated in a tail expression. Warnings from this lint will appear when running cargo fix --edition, but will otherwise not automatically make any changes. It is recommended to manually inspect the warnings and determine whether or not you need to make any adjustments.

If you want to manually inspect these warnings without performing the edition migration, you can enable the lint with:

#![allow(unused)] fn main() { // Add this to the root of your crate to do a manual migration. #![warn(tail_expr_drop_order)] }
1

Details are documented at RFC 3606

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

Match ergonomics reservations

Summary

  • Writing mut, ref, or ref mut on a binding is only allowed within a pattern when the pattern leading up to that binding is fully explicit (i.e. when it does not use match ergonomics).
    • Put differently, when the default binding mode is not move, writing mut, ref, or ref mut on a binding is an error.
  • Reference patterns (& or &mut) are only allowed within the fully-explicit prefix of a pattern.
    • Put differently, reference patterns can only match against references in the scrutinee when the default binding mode is move.

Details

Background

Within match, let, and other constructs, we match a pattern against a scrutinee. E.g.:

#![allow(unused)] fn main() { let &[&mut [ref x]] = &[&mut [()]]; // x: &() // ~~~~~~~~~~~~~~~ ~~~~~~~~~~~~ // Pattern Scrutinee }

Such a pattern is called fully explicit because it does not elide (i.e. "skip" or "pass") any references within the scrutinee. By contrast, this otherwise-equivalent pattern is not fully explicit:

#![allow(unused)] fn main() { let [[x]] = &[&mut [()]]; // x: &() }

Patterns such as this are said to be using match ergonomics, originally introduced in RFC 2005.

Under match ergonomics, as we incrementally match a pattern against a scrutinee, we keep track of the default binding mode. This mode can be one of move, ref mut, or ref, and it starts as move. When we reach a binding, unless an explicit binding mode is provided, the default binding mode is used to decide the binding's type.

For example, here we provide an explicit binding mode, causing x to be bound by reference:

#![allow(unused)] fn main() { let ref x = (); // &() }

By contrast:

#![allow(unused)] fn main() { let [x] = &[()]; // &() }

Here, in the pattern, we pass the outer shared reference in the scrutinee. This causes the default binding mode to switch from move to ref. Since there is no explicit binding mode specified, the ref binding mode is used when binding x.

mut restriction

In Rust 2021 and earlier editions, we allow this oddity:

#![allow(unused)] fn main() { let [x, mut y] = &[(), ()]; // x: &(), mut y: () }

Here, because we pass the shared reference in the pattern, the default binding mode switches to ref. But then, in these editions, writing mut on the binding resets the default binding mode to move.

This can be surprising as it's not intuitive that mutability should affect the type.

To leave space to fix this, in Rust 2024 it's an error to write mut on a binding when the default binding mode is not move. That is, mut can only be written on a binding when the pattern (leading up to that binding) is fully explicit.

In Rust 2024, we can write the above example as:

#![allow(unused)] fn main() { let &[ref x, mut y] = &[(), ()]; // x: &(), mut y: () }

ref / ref mut restriction

In Rust 2021 and earlier editions, we allow:

#![allow(unused)] fn main() { let [ref x] = &[()]; // x: &() }

Here, the ref explicit binding mode is redundant, as by passing the shared reference (i.e. not mentioning it in the pattern), the binding mode switches to ref.

To leave space for other language possibilities, we are disallowing explicit binding modes where they are redundant in Rust 2024. We can rewrite the above example as simply:

#![allow(unused)] fn main() { let [x] = &[()]; // x: &() }

Reference patterns restriction

In Rust 2021 and earlier editions, we allow this oddity:

#![allow(unused)] fn main() { let [&x, y] = &[&(), &()]; // x: (), y: &&() }

Here, the & in the pattern both matches against the reference on &() and resets the default binding mode to move. This can be surprising because the single & in the pattern causes a larger than expected change in the type by removing both layers of references.

To leave space to fix this, in Rust 2024 it's an error to write & or &mut in the pattern when the default binding mode is not move. That is, & or &mut can only be written when the pattern (leading up to that point) is fully explicit.

In Rust 2024, we can write the above example as:

#![allow(unused)] fn main() { let &[&x, ref y] = &[&(), &()]; }

Migration

The rust_2024_incompatible_pat lint flags patterns that are not allowed in Rust 2024. This lint is part of the rust-2024-compatibility lint group which is automatically applied when running cargo fix --edition. This lint will automatically convert affected patterns to fully explicit patterns that work correctly in Rust 2024 and in all prior editions.

To migrate your code to be compatible with Rust 2024, run:

cargo fix --edition

For example, this will convert this...

#![allow(unused)] fn main() { let [x, mut y] = &[(), ()]; let [ref x] = &[()]; let [&x, y] = &[&(), &()]; }

...into this:

#![allow(unused)] fn main() { let &[ref x, mut y] = &[(), ()]; let &[ref x] = &[()]; let &[&x, ref y] = &[&(), &()]; }

Alternatively, you can manually enable the lint to find patterns that will need to be migrated:

#![allow(unused)] fn main() { // Add this to the root of your crate to do a manual migration. #![warn(rust_2024_incompatible_pat)] }

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

Unsafe extern blocks

Summary

Details

Rust 1.82 added the ability in all editions to mark extern blocks with the unsafe keyword.1 Adding the unsafe keyword helps to emphasize that it is the responsibility of the author of the extern block to ensure that the signatures are correct. If the signatures are not correct, then it may result in undefined behavior.

The syntax for an unsafe extern block looks like this:

#![allow(unused)] fn main() { unsafe extern "C" { // sqrt (from libm) may be called with any `f64` pub safe fn sqrt(x: f64) -> f64; // strlen (from libc) requires a valid pointer, // so we mark it as being an unsafe fn pub unsafe fn strlen(p: *const std::ffi::c_char) -> usize; // this function doesn't say safe or unsafe, so it defaults to unsafe pub fn free(p: *mut core::ffi::c_void); pub safe static IMPORTANT_BYTES: [u8; 256]; } }

In addition to being able to mark an extern block as unsafe, you can also specify if individual items in the extern block are safe or unsafe. Items marked as safe can be used without an unsafe block.

Starting with the 2024 Edition, it is now required to include the unsafe keyword on an extern block. This is intended to make it very clear that there are safety requirements that must be upheld by the extern definitions.

1

See RFC 3484 for the original proposal.

Migration

The missing_unsafe_on_extern lint can update extern blocks to add the unsafe keyword. The lint is part of the rust-2024-compatibility lint group which is included in the automatic edition migration. In order to migrate your code to be Rust 2024 Edition compatible, run:

cargo fix --edition

Just beware that this automatic migration will not be able to verify that the signatures in the extern block are correct. It is still your responsibility to manually review their definition.

Alternatively, you can manually enable the lint to find places where there are unsafe blocks that need to be updated.

#![allow(unused)] fn main() { // Add this to the root of your crate to do a manual migration. #![warn(missing_unsafe_on_extern)] }

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

Unsafe attributes

Summary

Details

Rust 1.82 added the ability in all editions to mark certain attributes as unsafe to indicate that they have soundness requirements that must be upheld.1 The syntax for an unsafe attribute looks like this:

#![allow(unused)] fn main() { // SAFETY: there is no other global function of this name #[unsafe(no_mangle)] pub fn example() {} }

Marking the attribute with unsafe highlights that there are safety requirements that must be upheld that the compiler cannot verify on its own.

Starting with the 2024 Edition, it is now required to mark these attributes as unsafe. The following section describes the safety requirements for these attributes.

1

See RFC 3325 for the original proposal.

Safety requirements

The no_mangle, export_name, and link_section attributes influence the symbol names and linking behavior of items. Care must be taken to ensure that these attributes are used correctly.

Because the set of symbols across all linked libraries is a global namespace, there can be issues if there is a symbol name collision between libraries. Typically this isn't an issue for normally defined functions because symbol mangling helps ensure that the symbol name is unique. However, attributes like export_name can upset that assumption of uniqueness.

For example, in previous editions the following crashes on most Unix-like platforms despite containing only safe code:

fn main() { println!("Hello, world!"); } #[export_name = "malloc"] fn foo() -> usize { 1 }

In the 2024 Edition, it is now required to mark these attributes as unsafe to emphasize that it is required to ensure that the symbol is defined correctly:

#![allow(unused)] fn main() { // SAFETY: There should only be a single definition of the loop symbol. #[unsafe(export_name="loop")] fn arduino_loop() { // ... } }

Migration

The unsafe_attr_outside_unsafe lint can update these attributes to use the unsafe(...) format. The lint is part of the rust-2024-compatibility lint group which is included in the automatic edition migration. In order to migrate your code to be Rust 2024 Edition compatible, run:

cargo fix --edition

Just beware that this automatic migration will not be able to verify that these attributes are being used correctly. It is still your responsibility to manually review their usage.

Alternatively, you can manually enable the lint to find places where these attributes need to be updated.

#![allow(unused)] fn main() { // Add this to the root of your crate to do a manual migration. #![warn(unsafe_attr_outside_unsafe)] }

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

unsafe_op_in_unsafe_fn warning

Summary

  • The unsafe_op_in_unsafe_fn lint now warns by default. This warning detects calls to unsafe operations in unsafe functions without an explicit unsafe block.

Details

The unsafe_op_in_unsafe_fn lint will fire if there are unsafe operations in an unsafe function without an explicit unsafe {} block.

#![allow(unused)] fn main() { #![warn(unsafe_op_in_unsafe_fn)] unsafe fn get_unchecked<T>(x: &[T], i: usize) -> &T { x.get_unchecked(i) // WARNING: requires unsafe block } }

The solution is to wrap any unsafe operations in an unsafe block:

#![allow(unused)] fn main() { #![deny(unsafe_op_in_unsafe_fn)] unsafe fn get_unchecked<T>(x: &[T], i: usize) -> &T { unsafe { x.get_unchecked(i) } } }

This change is intended to help protect against accidental use of unsafe operations in an unsafe function. The unsafe function keyword was performing two roles. One was to declare that calling the function requires unsafe, and that the caller is responsible to uphold additional safety requirements. The other role was to allow the use of unsafe operations inside of the function. This second role was determined to be too risky without explicit unsafe blocks.

More information and motivation may be found in RFC #2585.

Migration

The unsafe_op_in_unsafe_fn lint is part of the rust-2024-compatibility lint group. In order to migrate your code to be Rust 2024 Edition compatible, run:

cargo fix --edition

Alternatively, you can manually enable the lint to find places where unsafe blocks need to be added, or switch it to allow to silence the lint completely.

#![allow(unused)] fn main() { // Add this to the root of your crate to do a manual migration. #![warn(unsafe_op_in_unsafe_fn)] }

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

Disallow references to static mut

Summary

  • The static_mut_refs lint level is now deny by default. This checks for taking a shared or mutable reference to a static mut.

Details

The static_mut_refs lint detects taking a reference to a static mut. In the 2024 Edition, this lint is now deny by default to emphasize that you should avoid making these references.

#![allow(unused)] fn main() { static mut X: i32 = 23; static mut Y: i32 = 24; unsafe { let y = &X; // ERROR: shared reference to mutable static let ref x = X; // ERROR: shared reference to mutable static let (x, y) = (&X, &Y); // ERROR: shared reference to mutable static } }

Merely taking such a reference in violation of Rust's mutability XOR aliasing requirement has always been instantaneous undefined behavior, even if the reference is never read from or written to. Furthermore, upholding mutability XOR aliasing for a static mut requires reasoning about your code globally, which can be particularly difficult in the face of reentrancy and/or multithreading.

Note that there are some cases where implicit references are automatically created without a visible & operator. For example, these situations will also trigger the lint:

#![allow(unused)] fn main() { static mut NUMS: &[u8; 3] = &[0, 1, 2]; unsafe { println!("{NUMS:?}"); // ERROR: shared reference to mutable static let n = NUMS.len(); // ERROR: shared reference to mutable static } }

Alternatives

Wherever possible, it is strongly recommended to use instead an immutable static of a type that provides interior mutability behind some locally-reasoned abstraction (which greatly reduces the complexity of ensuring that Rust's mutability XOR aliasing requirement is upheld).

In situations where no locally-reasoned abstraction is possible and you are therefore compelled still to reason globally about accesses to your static variable, you must now use raw pointers such as can be obtained via the &raw const or &raw mut operators. By first obtaining a raw pointer rather than directly taking a reference, (the safety requirements of) accesses through that pointer will be more familiar to unsafe developers and can be deferred until/limited to smaller regions of code.

Note that the following examples are just illustrations and are not intended as full-fledged implementations. Do not copy these as-is. There are details for your specific situation that may require alterations to fit your needs. These are intended to help you see different ways to approach your problem.

It is recommended to read the documentation for the specific types in the standard library, the reference on undefined behavior, the Rustonomicon, and if you are having questions to reach out on one of the Rust forums such as the Users Forum.

Don't use globals

This is probably something you already know, but if possible it is best to avoid mutable global state. Of course this can be a little more awkward or difficult at times, particularly if you need to pass a mutable reference around between many functions.

Atomics

The atomic types provide integers, pointers, and booleans that can be used in a static (without mut).

use std::sync::atomic::Ordering; use std::sync::atomic::AtomicU64; // Change from this: // static mut COUNTER: u64 = 0; // to this: static COUNTER: AtomicU64 = AtomicU64::new(0); fn main() { // Be sure to analyze your use case to determine the correct Ordering to use. COUNTER.fetch_add(1, Ordering::Relaxed); }

Mutex or RwLock

When your type is more complex than an atomic, consider using a Mutex or RwLock to ensure proper access to the global value.

use std::sync::Mutex; use std::collections::VecDeque; // Change from this: // static mut QUEUE: VecDeque<String> = VecDeque::new(); // to this: static QUEUE: Mutex<VecDeque<String>> = Mutex::new(VecDeque::new()); fn main() { QUEUE.lock().unwrap().push_back(String::from("abc")); let first = QUEUE.lock().unwrap().pop_front(); }

OnceLock or LazyLock

If you are using a static mut because you need to do some one-time initialization that can't be const, you can instead reach for OnceLock or LazyLock instead.

use std::sync::LazyLock; struct GlobalState; impl GlobalState { fn new() -> GlobalState { GlobalState } fn example(&self) {} } // Instead of some temporary or uninitialized type like: // static mut STATE: Option<GlobalState> = None; // use this instead: static STATE: LazyLock<GlobalState> = LazyLock::new(|| { GlobalState::new() }); fn main() { STATE.example(); }

OnceLock is similar to LazyLock, but can be used if you need to pass information into the constructor, which can work well with single initialization points (like main), or if the inputs are available wherever you access the global.

use std::sync::OnceLock; struct GlobalState; impl GlobalState { fn new(verbose: bool) -> GlobalState { GlobalState } fn example(&self) {} } struct Args { verbose: bool } fn parse_arguments() -> Args { Args { verbose: true } } static STATE: OnceLock<GlobalState> = OnceLock::new(); fn main() { let args = parse_arguments(); let state = GlobalState::new(args.verbose); let _ = STATE.set(state); // ... STATE.get().unwrap().example(); }

no_std one-time initialization

This example is similar to OnceLock in that it provides one-time initialization of a global, but it does not require std which is useful in a no_std context. Assuming your target supports atomics, then you can use an atomic to check for the initialization of the global. The pattern might look something like this:

use core::sync::atomic::AtomicUsize; use core::sync::atomic::Ordering; struct Args { verbose: bool, } fn parse_arguments() -> Args { Args { verbose: true } } struct GlobalState { verbose: bool, } impl GlobalState { const fn default() -> GlobalState { GlobalState { verbose: false } } fn new(verbose: bool) -> GlobalState { GlobalState { verbose } } fn example(&self) {} } const UNINITIALIZED: usize = 0; const INITIALIZING: usize = 1; const INITIALIZED: usize = 2; static STATE_INITIALIZED: AtomicUsize = AtomicUsize::new(UNINITIALIZED); static mut STATE: GlobalState = GlobalState::default(); fn set_global_state(state: GlobalState) { if STATE_INITIALIZED .compare_exchange( UNINITIALIZED, INITIALIZING, Ordering::SeqCst, Ordering::SeqCst, ) .is_ok() { // SAFETY: The reads and writes to STATE are guarded with the INITIALIZED guard. unsafe { STATE = state; } STATE_INITIALIZED.store(INITIALIZED, Ordering::SeqCst); } else { panic!("already initialized, or concurrent initialization"); } } fn get_state() -> &'static GlobalState { if STATE_INITIALIZED.load(Ordering::Acquire) != INITIALIZED { panic!("not initialized"); } else { // SAFETY: Mutable access is not possible after state has been initialized. unsafe { &*&raw const STATE } } } fn main() { let args = parse_arguments(); let state = GlobalState::new(args.verbose); set_global_state(state); // ... let state = get_state(); state.example(); }

This example assumes you can put some default value in the static before it is initialized (the const default constructor in this example). If that is not possible, consider using either MaybeUninit, or dynamic trait dispatch (with a dummy type that implements a trait), or some other approach to have a default placeholder.

There are community-provided crates that can provide similar one-time initialization, such as the static-cell crate (which supports targets that do not have atomics by using portable-atomic).

Raw pointers

In some cases you can continue to use static mut, but avoid creating references. For example, if you just need to pass raw pointers into a C library, don't create an intermediate reference. Instead you can use raw borrow operators, like in the following example:

#[repr(C)] struct GlobalState { value: i32 } impl GlobalState { const fn new() -> GlobalState { GlobalState { value: 0 } } } static mut STATE: GlobalState = GlobalState::new(); unsafe extern "C" { fn example_ffi(state: *mut GlobalState); } fn main() { unsafe { // Change from this: // example_ffi(&mut STATE as *mut GlobalState); // to this: example_ffi(&raw mut STATE); } }

Just beware that you still need to uphold the aliasing constraints around mutable pointers. This may require some internal or external synchronization or proofs about how it is used across threads, interrupt handlers, and reentrancy.

UnsafeCell with Sync

UnsafeCell does not impl Sync, so it cannot be used in a static. You can create your own wrapper around UnsafeCell to add a Sync impl so that it can be used in a static to implement interior mutability. This approach can be useful if you have external locks or other guarantees that uphold the safety invariants required for mutable pointers.

Note that this is largely the same as the raw pointers example. The wrapper helps to emphasize how you are using the type, and focus on which safety requirements you should be careful of. But otherwise they are roughly the same.

#![allow(unused)] fn main() { use std::cell::UnsafeCell; fn with_interrupts_disabled<T: Fn()>(f: T) { // A real example would disable interrupts. f(); } #[repr(C)] struct GlobalState { value: i32, } impl GlobalState { const fn new() -> GlobalState { GlobalState { value: 0 } } } #[repr(transparent)] pub struct SyncUnsafeCell<T>(UnsafeCell<T>); unsafe impl<T: Sync> Sync for SyncUnsafeCell<T> {} static STATE: SyncUnsafeCell<GlobalState> = SyncUnsafeCell(UnsafeCell::new(GlobalState::new())); fn set_value(value: i32) { with_interrupts_disabled(|| { let state = STATE.0.get(); unsafe { // SAFETY: This value is only ever read in our interrupt handler, // and interrupts are disabled, and we only use this in one thread. (*state).value = value; } }); } }

The standard library has a nightly-only (unstable) variant of UnsafeCell called SyncUnsafeCell. This example above shows a very simplified version of the standard library type, but would be used roughly the same way. It can provide even better isolation, so do check out its implementation for more details.

This example includes a fictional with_interrupts_disabled function which is the type of thing you might see in an embedded environment. For example, the critical-section crate provides a similar kind of functionality that could be used for an embedded environment.

Safe references

In some cases it may be safe to create a reference of a static mut. The whole point of the static_mut_refs lint is that this is very hard to do correctly! However, that's not to say it is impossible. If you have a situation where you can guarantee that the aliasing requirements are upheld, such as guaranteeing the static is narrowly scoped (only used in a small module or function), has some internal or external synchronization, accounts for interrupt handlers and reentrancy, panic safety, drop handlers, etc., then taking a reference may be fine.

There are two approaches you can take for this. You can either allow the static_mut_refs lint (preferably as narrowly as you can), or convert raw pointers to a reference, as with &mut *&raw mut MY_STATIC.

Short-lived references

If you must create a reference to a static mut, then it is recommended to minimize the scope of how long that reference exists. Avoid squirreling the reference away somewhere, or keeping it alive through a large section of code. Keeping it short-lived helps with auditing, and verifying that exclusive access is maintained for the duration. Using pointers should be your default unit, and only convert the pointer to a reference on demand when absolutely required.

Migration

There is no automatic migration to fix these references to static mut. To avoid undefined behavior you must rewrite your code to use a different approach as recommended in the Alternatives section.

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

Never type fallback change

Summary

Details

When the compiler sees a value of type ! (never) in a coercion site, it implicitly inserts a coercion to allow the type checker to infer any type:

#![allow(unused)] fn main() { #![feature(never_type)] // This: let x: u8 = panic!(); // ...is (essentially) turned by the compiler into: let x: u8 = absurd(panic!()); // ...where `absurd` is the following function // (it's sound because `!` always marks unreachable code): fn absurd<T>(x: !) -> T { x } }

This can lead to compilation errors if the type cannot be inferred:

#![allow(unused)] fn main() { #![feature(never_type)] fn absurd<T>(x: !) -> T { x } // This: { panic!() }; // ...gets turned into this: { absurd(panic!()) }; //~ ERROR can't infer the type of `absurd` }

To prevent such errors, the compiler remembers where it inserted absurd calls, and if it can't infer the type, it uses the fallback type instead:

#![allow(unused)] fn main() { #![feature(never_type)] fn absurd<T>(x: !) -> T { x } type Fallback = /* An arbitrarily selected type! */ !; { absurd::<Fallback>(panic!()) } }

This is what is known as "never type fallback".

Historically, the fallback type has been () (unit). This caused ! to spontaneously coerce to () even when the compiler would not infer () without the fallback. That was confusing and has prevented the stabilization of the ! type.

In the 2024 edition, the fallback type is now !. (We plan to make this change across all editions at a later date.) This makes things work more intuitively. Now when you pass ! and there is no reason to coerce it to something else, it is kept as !.

In some cases your code might depend on the fallback type being (), so this can cause compilation errors or changes in behavior.

never_type_fallback_flowing_into_unsafe

The default level of the never_type_fallback_flowing_into_unsafe lint has been raised from warn to deny in the 2024 Edition. This lint helps detect a particular interaction with the fallback to ! and unsafe code which may lead to undefined behavior. See the link for a complete description.

Migration

There is no automatic fix, but there is automatic detection of code that will be broken by the edition change. While still on a previous edition you will see warnings if your code will be broken.

The fix is to specify the type explicitly so that the fallback type is not used. Unfortunately, it might not be trivial to see which type needs to be specified.

One of the most common patterns broken by this change is using f()?; where f is generic over the Ok-part of the return type:

#![allow(unused)] fn main() { #![allow(dependency_on_unit_never_type_fallback)] fn outer<T>(x: T) -> Result<T, ()> { fn f<T: Default>() -> Result<T, ()> { Ok(T::default()) } f()?; Ok(x) } }

You might think that, in this example, type T can't be inferred. However, due to the current desugaring of the ? operator, it was inferred as (), and it will now be inferred as !.

To fix the issue you need to specify the T type explicitly:

#![allow(unused)] fn main() { #![deny(dependency_on_unit_never_type_fallback)] fn outer<T>(x: T) -> Result<T, ()> { fn f<T: Default>() -> Result<T, ()> { Ok(T::default()) } f::<()>()?; // ...or: () = f()?; Ok(x) } }

Another relatively common case is panicking in a closure:

#![allow(unused)] fn main() { #![allow(dependency_on_unit_never_type_fallback)] trait Unit {} impl Unit for () {} fn run<R: Unit>(f: impl FnOnce() -> R) { f(); } run(|| panic!()); }

Previously ! from the panic! coerced to () which implements Unit. However now the ! is kept as ! so this code fails because ! doesn't implement Unit. To fix this you can specify the return type of the closure:

#![allow(unused)] fn main() { #![deny(dependency_on_unit_never_type_fallback)] trait Unit {} impl Unit for () {} fn run<R: Unit>(f: impl FnOnce() -> R) { f(); } run(|| -> () { panic!() }); }

A similar case to that of f()? can be seen when using a !-typed expression in one branch and a function with an unconstrained return type in the other:

#![allow(unused)] fn main() { #![allow(dependency_on_unit_never_type_fallback)] if true { Default::default() } else { return }; }

Previously () was inferred as the return type of Default::default() because ! from return was spuriously coerced to (). Now, ! will be inferred instead causing this code to not compile because ! does not implement Default.

Again, this can be fixed by specifying the type explicitly:

#![allow(unused)] fn main() { #![deny(dependency_on_unit_never_type_fallback)] () = if true { Default::default() } else { return }; // ...or: if true { <() as Default>::default() } else { return }; }

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

Macro Fragment Specifiers

Summary

  • The expr fragment specifier now also supports const and _ expressions.
  • The expr_2021 fragment specifier has been added for backwards compatibility.

Details

As new syntax is added to Rust, existing macro_rules fragment specifiers are sometimes not allowed to match on the new syntax in order to retain backwards compatibility. Supporting the new syntax in the old fragment specifiers is sometimes deferred until the next edition, which provides an opportunity to update them.

Indeed this happened with const expressions added in 1.79 and _ expressions added in 1.59. In the 2021 Edition and earlier, the expr fragment specifier does not match those expressions. This is because you may have a scenario like:

macro_rules! example { ($e:expr) => { println!("first rule"); }; (const $e:expr) => { println!("second rule"); }; } fn main() { example!(const { 1 + 1 }); }

Here, in the 2021 Edition, the macro will match the second rule. If earlier editions had changed expr to match the newly introduced const expressions, then it would match the first rule, which would be a breaking change.

In the 2024 Edition, expr specifiers now also match const and _ expressions. To support the old behavior, the expr_2021 fragment specifier has been added which does not match the new expressions.

Migration

The edition_2024_expr_fragment_specifier lint will change all uses of the expr specifier to expr_2021 to ensure that the behavior of existing macros does not change. The lint is part of the rust-2024-compatibility lint group which is included in the automatic edition migration. In order to migrate your code to be Rust 2024 Edition compatible, run:

cargo fix --edition

In most cases, you will likely want to keep the expr specifier instead, in order to support the new expressions. You will need to review your macro to determine if there are other rules that would otherwise match with const or _ and determine if there is a conflict. If you want the new behavior, just revert any changes made by the lint.

Alternatively, you can manually enable the lint to find macros where you may need to update the expr specifier.

#![allow(unused)] fn main() { // Add this to the root of your crate to do a manual migration. #![warn(edition_2024_expr_fragment_specifier)] }

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

Missing macro fragment specifiers

Summary

Details

The missing_fragment_specifier lint detects a situation when an unused pattern in a macro_rules! macro definition has a meta-variable (e.g. $e) that is not followed by a fragment specifier (e.g. :expr). This is now a hard error in the 2024 Edition.

macro_rules! foo { () => {}; ($name) => { }; // ERROR: missing fragment specifier } fn main() { foo!(); }

Calling the macro with arguments that would match a rule with a missing specifier (e.g., foo!($name)) is a hard error in all editions. However, simply defining a macro with missing fragment specifiers is not, though we did add a lint in Rust 1.17.

We'd like to make this a hard error in all editions, but there would be too much breakage right now. So we're starting by making this a hard error in Rust 2024.1

1

The lint is marked as a "future-incompatible" warning to indicate that it may become a hard error in all editions in a future release. See #40107 for more information.

Migration

To migrate your code to the 2024 Edition, remove the unused matcher rule from the macro. The missing_fragment_specifier lint is on by default in all editions, and should alert you to macros with this issue.

There is no automatic migration for this change. We expect that this style of macro is extremely rare. The lint has been a future-incompatibility lint since Rust 1.17, a deny-by-default lint since Rust 1.20, and since Rust 1.82, it has warned about dependencies that are using this pattern.

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

gen keyword

Summary

Details

The gen keyword has been reserved as part of RFC #3513 to introduce "gen blocks" in a future release of Rust. gen blocks will provide a way to make it easier to write certain kinds of iterators. Reserving the keyword now will make it easier to stabilize gen blocks before the next edition.

Migration

Introducing the gen keyword can cause a problem for any identifiers that are already called gen. For example, any variable or function name called gen would clash with the new keyword. To overcome this, Rust supports the r# prefix for a raw identifier, which allows identifiers to overlap with keywords.

The keyword_idents_2024 lint will automatically modify any identifier named gen to be r#gen so that code continues to work on both editions. This lint is part of the rust-2024-compatibility lint group, which will automatically be applied when running cargo fix --edition. To migrate your code to be Rust 2024 Edition compatible, run:

cargo fix --edition

For example, this will change:

fn gen() { println!("generating!"); } fn main() { gen(); }

to be:

fn r#gen() { println!("generating!"); } fn main() { r#gen(); }

Alternatively, you can manually enable the lint to find places where gen identifiers need to be modified to r#gen:

#![allow(unused)] fn main() { // Add this to the root of your crate to do a manual migration. #![warn(keyword_idents_2024)] }

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

Reserved syntax

Summary

  • Unprefixed guarded strings of the form #"foo"# are reserved for future use.
  • Two or more # characters are reserved for future use.

Details

RFC 3593 reserved syntax in the 2024 Edition for guarded string literals that do not have a prefix to make room for possible future language changes. The 2021 Edition reserved syntax for guarded strings with a prefix, such as ident##"foo"##. The 2024 Edition extends that to also reserve strings without the ident prefix.

There are two reserved syntaxes:

  • One or more # characters immediately followed by a string literal.
  • Two or more # characters in a row (not separated by whitespace).

This reservation is done across an edition boundary because of interactions with tokenization and macros. For example, consider this macro:

#![allow(unused)] fn main() { macro_rules! demo { ( $a:tt ) => { println!("one token") }; ( $a:tt $b:tt $c:tt ) => { println!("three tokens") }; } demo!("foo"); demo!(r#"foo"#); demo!(#"foo"#); demo!(###) }

Prior to the 2024 Edition, this produces:

one token one token three tokens three tokens

Starting in the 2024 Edition, the #"foo"# line and the ### line now generates a compile error because those forms are now reserved.

Migration

The rust_2024_guarded_string_incompatible_syntax lint will identify any tokens that match the reserved syntax, and will suggest a modification to insert spaces where necessary to ensure the tokens continue to be parsed separately.

The lint is part of the rust-2024-compatibility lint group which is included in the automatic edition migration. In order to migrate your code to be Rust 2024 Edition compatible, run:

cargo fix --edition

Alternatively, you can manually enable the lint to find macro calls where you may need to update the tokens:

#![allow(unused)] fn main() { // Add this to the root of your crate to do a manual migration. #![warn(rust_2024_guarded_string_incompatible_syntax)] }

標準ライブラリ

以降の節では、2024 エディションでの標準ライブラリへのアップデートについて詳説します。

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

Changes to the prelude

Summary

  • The Future and IntoFuture traits are now part of the prelude.
  • This might make calls to trait methods ambiguous which could make some code fail to compile.

Details

The prelude of the standard library is the module containing everything that is automatically imported in every module. It contains commonly used items such as Option, Vec, drop, and Clone.

The Rust compiler prioritizes any manually imported items over those from the prelude, to make sure additions to the prelude will not break any existing code. For example, if you have a crate or module called example containing a pub struct Option;, then use example::*; will make Option unambiguously refer to the one from example; not the one from the standard library.

However, adding a trait to the prelude can break existing code in a subtle way. For example, a call to x.poll() which comes from a MyPoller trait might fail to compile if std's Future is also imported, because the call to poll is now ambiguous and could come from either trait.

As a solution, Rust 2024 will use a new prelude. It's identical to the current one, except for the following changes:

Migration

Conflicting trait methods

When two traits that are in scope have the same method name, it is ambiguous which trait method should be used. For example:

trait MyPoller { // This name is the same as the `poll` method on the `Future` trait from `std`. fn poll(&self) { println!("polling"); } } impl<T> MyPoller for T {} fn main() { // Pin<&mut async {}> implements both `std::future::Future` and `MyPoller`. // If both traits are in scope (as would be the case in Rust 2024), // then it becomes ambiguous which `poll` method to call core::pin::pin!(async {}).poll(); }

We can fix this so that it works on all editions by using fully qualified syntax:

fn main() { // Now it is clear which trait method we're referring to <_ as MyPoller>::poll(&core::pin::pin!(async {})); }

The rust_2024_prelude_collisions lint will automatically modify any ambiguous method calls to use fully qualified syntax. This lint is part of the rust-2024-compatibility lint group, which will automatically be applied when running cargo fix --edition. To migrate your code to be Rust 2024 Edition compatible, run:

cargo fix --edition

Alternatively, you can manually enable the lint to find places where these qualifications need to be added:

#![allow(unused)] fn main() { // Add this to the root of your crate to do a manual migration. #![warn(rust_2024_prelude_collisions)] }

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

Add IntoIterator for Box<[T]>

Summary

  • Boxed slices implement IntoIterator in all editions.
  • Calls to IntoIterator::into_iter are hidden in editions prior to 2024 when using method call syntax (i.e., boxed_slice.into_iter()). So, boxed_slice.into_iter() still resolves to (&(*boxed_slice)).into_iter() as it has before.
  • boxed_slice.into_iter() changes meaning to call IntoIterator::into_iter in Rust 2024.

Details

Until Rust 1.80, IntoIterator was not implemented for boxed slices. In prior versions, if you called .into_iter() on a boxed slice, the method call would automatically dereference from Box<[T]> to &[T], and return an iterator that yielded references of &T. For example, the following worked in prior versions:

#![allow(unused)] fn main() { // Example of behavior in previous editions. let my_boxed_slice: Box<[u32]> = vec![1, 2, 3].into_boxed_slice(); // Note: .into_iter() was required in versions older than 1.80 for x in my_boxed_slice.into_iter() { // x is of type &u32 in editions prior to 2024 } }

In Rust 1.80, implementations of IntoIterator were added for boxed slices. This allows iterating over elements of the slice by-value instead of by-reference:

#![allow(unused)] fn main() { // NEW as of 1.80, all editions let my_boxed_slice: Box<[u32]> = vec![1, 2, 3].into_boxed_slice(); for x in my_boxed_slice { // notice no need for calling .into_iter() // x is of type u32 } }

This example is allowed on all editions because previously this was an error since for loops do not automatically dereference like the .into_iter() method call does.

However, this would normally be a breaking change because existing code that manually called .into_iter() on a boxed slice would change from having an iterator over references to an iterator over values. To resolve this problem, method calls of .into_iter() on boxed slices have edition-dependent behavior. In editions before 2024, it continues to return an iterator over references, and starting in Edition 2024 it returns an iterator over values.

#![allow(unused)] fn main() { // Example of changed behavior in Edition 2024 let my_boxed_slice: Box<[u32]> = vec![1, 2, 3].into_boxed_slice(); // Example of old code that still manually calls .into_iter() for x in my_boxed_slice.into_iter() { // x is now type u32 in Edition 2024 } }

Migration

The boxed_slice_into_iter lint will automatically modify any calls to .into_iter() on boxed slices to call .iter() instead to retain the old behavior of yielding references. This lint is part of the rust-2024-compatibility lint group, which will automatically be applied when running cargo fix --edition. To migrate your code to be Rust 2024 Edition compatible, run:

cargo fix --edition

For example, this will change:

fn main() { let my_boxed_slice: Box<[u32]> = vec![1, 2, 3].into_boxed_slice(); for x in my_boxed_slice.into_iter() { // x is of type &u32 } }

to be:

fn main() { let my_boxed_slice: Box<[u32]> = vec![1, 2, 3].into_boxed_slice(); for x in my_boxed_slice.iter() { // x is of type &u32 } }

The boxed_slice_into_iter lint is defaulted to warn on all editions, so unless you have manually silenced the lint, you should already see it before you migrate.

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

Unsafe functions

Summary

Details

Over time it has become evident that certain functions in the standard library should have been marked as unsafe. However, adding unsafe to a function can be a breaking change since it requires existing code to be placed in an unsafe block. To avoid the breaking change, these functions are marked as unsafe starting in the 2024 Edition, while not requiring unsafe in previous editions.

std::env::{set_var, remove_var}

It can be unsound to call std::env::set_var or std::env::remove_var in a multi-threaded program due to safety limitations of the way the process environment is handled on some platforms. The standard library originally defined these as safe functions, but it was later determined that was not correct.

It is important to ensure that these functions are not called when any other thread might be running. See the Safety section of the function documentation for more details.

std::os::unix::process::CommandExt::before_exec

The std::os::unix::process::CommandExt::before_exec function is a unix-specific function which provides a way to run a closure before calling exec. This function was deprecated in the 1.37 release, and replaced with pre_exec which does the same thing, but is marked as unsafe.

Even though before_exec is deprecated, it is now correctly marked as unsafe starting in the 2024 Edition. This should help ensure that any legacy code which has not already migrated to pre_exec to require an unsafe block.

There are very strict safety requirements for the before_exec closure to satisfy. See the Safety section for more details.

Migration

To make your code compile in both the 2021 and 2024 editions, you will need to make sure that these functions are called only from within unsafe blocks.

⚠ Caution: It is important that you manually inspect the calls to these functions and possibly rewrite your code to satisfy the preconditions of those functions. In particular, set_var and remove_var should not be called if there might be multiple threads running. You may need to elect to use a different mechanism other than environment variables to manage your use case.

The deprecated_safe_2024 lint will automatically modify any use of these functions to be wrapped in an unsafe block so that it can compile on both editions. This lint is part of the rust-2024-compatibility lint group, which will automatically be applied when running cargo fix --edition. To migrate your code to be Rust 2024 Edition compatible, run:

cargo fix --edition

For example, this will change:

fn main() { std::env::set_var("FOO", "123"); }

to be:

fn main() { // TODO: Audit that the environment access only happens in single-threaded code. unsafe { std::env::set_var("FOO", "123") }; }

Just beware that this automatic migration will not be able to verify that these functions are being used correctly. It is still your responsibility to manually review their usage.

Alternatively, you can manually enable the lint to find places these functions are called:

#![allow(unused)] fn main() { // Add this to the root of your crate to do a manual migration. #![warn(deprecated_safe_2024)] }

Cargo

以降の節では、2024 エディションでの Cargo へのアップデートについて詳説します。

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

Cargo: Rust-version aware resolver

Summary

  • edition = "2024" implies resolver = "3" in Cargo.toml which enables a Rust-version aware dependency resolver.

Details

Since Rust 1.84.0, Cargo has opt-in support for compatibility with package.rust-version to be considered when selecting dependency versions by setting resolver.incompatible-rust-version = "fallback" in .cargo/config.toml.

Starting in Rust 2024, this will be the default. That is, writing edition = "2024" in Cargo.toml will imply resolver = "3" which will imply resolver.incompatible-rust-version = "fallback".

The resolver is a global setting for a workspace, and the setting is ignored in dependencies. The setting is only honored for the top-level package of the workspace. If you are using a virtual workspace, you will still need to explicitly set the resolver field in the [workspace] definition if you want to opt-in to the new resolver.

For more details on how Rust-version aware dependency resolution works, see the Cargo book.

Migration

There are no automated migration tools for updating for the new resolver.

We recommend projects verify against the latest dependencies in CI to catch bugs in dependencies as soon as possible.

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

Cargo: Table and key name consistency

Summary

  • Several table and key names in Cargo.toml have been removed where there were previously two ways to specify the same thing.
    • Removed [project]; use [package] instead.
    • Removed default_features; use default-features instead.
    • Removed crate_type; use crate-type instead.
    • Removed proc_macro; use proc-macro instead.
    • Removed dev_dependencies; use dev-dependencies instead.
    • Removed build_dependencies; use build-dependencies instead.

Details

Several table and keys names are no longer allowed in the 2024 Edition. There were two ways to specify these tables or keys, and this helps ensure there is only one way to specify them.

Some were due to a change in decisions over time, and some were inadvertent implementation artifacts. In order to avoid confusion, and to enforce a single style for specifying these tables and keys, only one variant is now allowed.

For example:

[dev_dependencies] rand = { version = "0.8.5", default_features = false }

Should be changed to:

[dev-dependencies] rand = { version = "0.8.5", default-features = false }

Notice that the underscores were changed to dashes for dev_dependencies and default_features.

Migration

When using cargo fix --edition, Cargo will automatically update your Cargo.toml file to use the preferred table and key names.

If you would prefer to update your Cargo.toml manually, be sure to go through the list above and make sure only the new forms are used.

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

Cargo: Reject unused inherited default-features

Summary

  • default-features = false is no longer allowed in an inherited workspace dependency if the workspace dependency specifies default-features = true (or does not specify default-features).

Details

Workspace inheritance allows you to specify dependencies in one place (the workspace), and then to refer to those workspace dependencies from within a package. There was an inadvertent interaction with how default-features is specified that is no longer allowed in the 2024 Edition.

Unless the workspace specifies default-features = false, it is no longer allowed to specify default-features = false in an inherited package dependency. For example, with a workspace that specifies:

[workspace.dependencies] regex = "1.10.4"

The following is now an error:

[package] name = "foo" version = "1.0.0" edition = "2024" [dependencies] regex = { workspace = true, default-features = false } # ERROR

The reason for this change is to avoid confusion when specifying default-features = false when the default feature is already enabled, since it has no effect.

If you want the flexibility of deciding whether or not a dependency enables the default-features of a dependency, be sure to set default-features = false in the workspace definition. Just beware that if you build multiple workspace members at the same time, the features will be unified so that if one member sets default-features = true (which is the default if not explicitly set), the default-features will be enabled for all members using that dependency.

Migration

When using cargo fix --edition, Cargo will automatically update your Cargo.toml file to remove default-features = false in this situation.

If you would prefer to update your Cargo.toml manually, check for any warnings when running a build and remove the corresponding entries. Previous editions should display something like:

warning: /home/project/Cargo.toml: `default-features` is ignored for regex, since `default-features` was not specified for `workspace.dependencies.regex`, this could become a hard error in the future

Rustdoc

以降の節では、2024 エディションでの Rustdoc へのアップデートについて詳説します。

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

Rustdoc combined tests

Summary

  • Doctests are now combined into a single binary which should result in a significant performance improvement.

Details

Prior the the 2024 Edition, rustdoc's "test" mode would compile each code block in your documentation as a separate executable. Although this was relatively simple to implement, it resulted in a significant performance burden when there were a large number of documentation tests. Starting with the 2024 Edition, rustdoc will attempt to combine documentation tests into a single binary, significantly reducing the overhead for compiling doctests.

#![allow(unused)] fn main() { /// Adds two numbers /// /// ``` /// assert_eq!(add(1, 1), 2); /// ``` pub fn add(left: u64, right: u64) -> u64 { left + right } /// Subtracts two numbers /// /// ``` /// assert_eq!(subtract(2, 1), 1); /// ``` pub fn subtract(left: u64, right: u64) -> u64 { left - right } }

In this example, the two doctests will now be compiled into a single executable. Rustdoc will essentially place each example in a separate function within a single binary. The tests still run in independent processes as they did before, so any global state (like global statics) should still continue to work correctly.1

This change is only available in the 2024 Edition to avoid potential incompatibilities with existing doctests which may not work in a combined executable. However, these incompatibilities are expected to be extremely rare.

1

For more information on the details of how this work, see "Doctests - How were they improved?".

standalone_crate tag

In some situations it is not possible for rustdoc to combine examples into a single executable. Rustdoc will attempt to automatically detect if this is not possible. For example, a test will not be combined with others if it:

  • Uses the compile_fail tag, which indicates that the example should fail to compile.
  • Uses an edition tag, which indicates the edition of the example.2
  • Uses global attributes, like the global_allocator attribute, which could potentially interfere with other tests.
  • Defines any crate-wide attributes (like #![feature(...)]).
  • Defines a macro that uses $crate, because the $crate path will not work correctly.

However, rustdoc is not able to automatically determine all situations where an example cannot be combined with other examples. In these situations, you can add the standalone_crate language tag to indicate that the example should be built as a separate executable. For example:

#![allow(unused)] fn main() { //! ``` //! let location = std::panic::Location::caller(); //! assert_eq!(location.line(), 5); //! ``` }

This is sensitive to the code structure of how the example is compiled and won't work with the "combined" approach because the line numbers will shift depending on how the doctests are combined. In these situations, you can add the standalone_crate tag to force the example to be built separately just as it was in previous editions. E.g.:

#![allow(unused)] fn main() { //! ```standalone_crate //! let location = std::panic::Location::caller(); //! assert_eq!(location.line(), 5); //! ``` }
2

Note that rustdoc will only combine tests if the entire crate is Edition 2024 or greater. Using the edition2024 tag in older editions will not result in those tests being combined.

Migration

There is no automatic migration to determine which doctests need to be annotated with the standalone_crate tag. It's very unlikely that any given doctest will not work correctly when migrated. We suggest that you update your crate to the 2024 Edition and then run your documentation tests and see if any fail. If one does, you will need to analyze whether it can be rewritten to be compatible with the combined approach, or alternatively, add the standalone_crate tag to retain the previous behavior.

Some things to watch out for and avoid are:

  • Checking the values of std::panic::Location or things that make use of Location. The location of the code is now different since multiple tests are now located in the same test crate.
  • Checking the value of std::any::type_name, which now has a different module path.

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

Rustdoc nested include! change

Summary

When a doctest is included with include_str!, if that doctest itself also uses include!, include_str!, or include_bytes!, the path is resolved relative to the Markdown file, rather than to the Rust source file.

Details

Prior to the 2024 edition, adding documentation with #[doc=include_str!("path/file.md")] didn't carry span information into any doctests in that file. As a result, if the Markdown file was in a different directory than the source, any paths included had to be specified relative to the source file.

For example, consider a library crate with these files:

  • Cargo.toml
  • README.md
  • src/
    • lib.rs
  • examples/
    • data.bin

Let's say that lib.rs contains this:

#![doc=include_str!("../README.md")]

And assume this README.md file:

``` let _ = include_bytes!("../examples/data.bin"); // ^^^ notice this ```

Prior to the 2024 edition, the path in README.md needed to be relative to the lib.rs file. In 2024 and later, it is now relative to README.md itself, so we would update README.md to:

``` let _ = include_bytes!("examples/data.bin"); ```

Migration

There is no automatic migration to convert the paths in affected doctests. If one of your doctests is affected, you'll see an error like this after migrating to the new edition when building your tests:

error: couldn't read `../examples/data.bin`: No such file or directory (os error 2) --> src/../README.md:2:24 | 2 | let _ = include_bytes!("../examples/data.bin"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: this error originates in the macro `include_bytes` (in Nightly builds, run with -Z macro-backtrace for more info) help: there is a file with the same name in a different directory | 2 | let _ = include_bytes!("examples/data.bin"); | ~~~~~~~~~~~~~~~~~~~

To migrate your doctests to Rust 2024, update any affected paths to be relative to the file containing the doctests.

Rustfmt

以降の節では、2024 エディションでの Rustfmt へのアップデートについて詳説します。

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

Rustfmt: Style edition

Summary

User can now control which style edition to use with rustfmt.

Details

The default formatting produced by Rustfmt is governed by the rules in the Rust Style Guide.

Additionally, Rustfmt has a formatting stability guarantee that aims to avoid causing noisy formatting churn for users when updating a Rust toolchain. This stability guarantee essentially means that a newer version of Rustfmt cannot modify the successfully formatted output that was produced by a previous version of Rustfmt.

The combination of those two constraints had historically locked both the Style Guide and the default formatting behavior in Rustfmt. This impasse caused various challenges, such as preventing the ability to iterate on style improvements, and requiring Rustfmt to maintain legacy formatting quirks that were obviated long ago (e.g. nested tuple access).

RFC 3338 resolved this impasse by establishing a mechanism for the Rust Style Guide to be aligned to Rust's Edition model wherein the Style Guide could evolve across Editions, and rustfmt would allow users to specify their desired Edition of the Style Guide, referred to as the Style Edition.

In the 2024 Edition, rustfmt now supports the ability for users to control the Style Edition used for formatting. The 2024 Edition of the Style Guide also includes enhancements to the Style Guide which are detailed elsewhere in this Edition Guide.

By default rustfmt will use the same Style Edition as the standard Rust Edition used for parsing, but the Style Edition can also be overridden and configured separately.

There are multiple ways to run rustfmt with the 2024 Style Edition:

With a Cargo.toml file that has edition set to 2024, run:

cargo fmt

Or run rustfmt directly with 2024 for the edition to use the 2024 edition for both parsing and the 2024 edition of the Style Guide:

rustfmt lib.rs --edition 2024

The style edition can also be set in a rustfmt.toml configuration file:

style_edition = "2024"

Which is then used when running rustfmt directly:

rustfmt lib.rs

Alternatively, the style edition can be specified directly from rustfmt options:

rustfmt lib.rs --style-edition 2024

Migration

Running cargo fmt or rustfmt with the 2024 edition or style edition will automatically migrate formatting over to the 2024 style edition formatting.

Projects who have contributors that may utilize their editor's format-on-save features are also strongly encouraged to add a .rustfmt.toml file to their project that includes the corresponding style_edition utilized within their project, or to encourage their users to ensure their local editor format-on-save feature is configured to use that same style_edition.

This is to ensure that the editor format-on-save output is consistent with the output when cargo fmt is manually executed by the developer, or the project's CI process (many editors will run rustfmt directly which by default uses the 2015 edition, whereas cargo fmt uses the edition specified in the Cargo.toml file)

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

Rustfmt: Formatting fixes

Summary

  • Fixes to various formatting scenarios.

Details

The 2024 style edition introduces several fixes to various formatting scenarios.

Don't align unrelated trailing comments after items or at the end of blocks

Previously rustfmt would assume that a comment on a line following an item with a trailing comment should be indented to match the trailing comment. This has been changed so that those comments are not indented.

Style edition 2021:

pub const IFF_MULTICAST: ::c_int = 0x0000000800; // Supports multicast // Multicast using broadcst. add. pub const SQ_CRETAB: u16 = 0x000e; // CREATE TABLE pub const SQ_DRPTAB: u16 = 0x000f; // DROP TABLE pub const SQ_CREIDX: u16 = 0x0010; // CREATE INDEX //const SQ_DRPIDX: u16 = 0x0011; // DROP INDEX //const SQ_GRANT: u16 = 0x0012; // GRANT //const SQ_REVOKE: u16 = 0x0013; // REVOKE fn foo() { let f = bar(); // Donec consequat mi. Quisque vitae dolor. Integer lobortis. Maecenas id nulla. Lorem. // Id turpis. Nam posuere lectus vitae nibh. Etiam tortor orci, sagittis // malesuada, rhoncus quis, hendrerit eget, libero. Quisque commodo nulla at let b = baz(); let normalized = self.ctfont.all_traits().normalized_weight(); // [-1.0, 1.0] // TODO(emilio): It may make sense to make this range [.01, 10.0], to align // with css-fonts-4's range of [1, 1000]. }

Style edition 2024:

pub const IFF_MULTICAST: ::c_int = 0x0000000800; // Supports multicast // Multicast using broadcst. add. pub const SQ_CRETAB: u16 = 0x000e; // CREATE TABLE pub const SQ_DRPTAB: u16 = 0x000f; // DROP TABLE pub const SQ_CREIDX: u16 = 0x0010; // CREATE INDEX //const SQ_DRPIDX: u16 = 0x0011; // DROP INDEX //const SQ_GRANT: u16 = 0x0012; // GRANT //const SQ_REVOKE: u16 = 0x0013; // REVOKE fn foo() { let f = bar(); // Donec consequat mi. Quisque vitae dolor. Integer lobortis. Maecenas id nulla. Lorem. // Id turpis. Nam posuere lectus vitae nibh. Etiam tortor orci, sagittis // malesuada, rhoncus quis, hendrerit eget, libero. Quisque commodo nulla at let b = baz(); let normalized = self.ctfont.all_traits().normalized_weight(); // [-1.0, 1.0] // TODO(emilio): It may make sense to make this range [.01, 10.0], to align // with css-fonts-4's range of [1, 1000]. }

Don't indent strings in comments

Previously rustfmt would incorrectly attempt to format strings in comments.

Original:

pub fn main() { /* let s = String::from( " hello world ", ); */ }

Style edition 2021:

pub fn main() { /* let s = String::from( " hello world ", ); */ }

Style edition 2024:

No change from original.

Long strings don't prevent formatting expressions

In some situations, long strings would previously prevent the expression from being formatted.

Style edition 2021:

fn main() { let value = if x == "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." { 0 } else {10}; let x = Testing { foo: "long_long_long_long_long_long_long_lo_long_long_long_long_long_long__long_long_long_long_long_long_", bar: "long_long_long_long_long_long_long_long_long_long_lo_long_long_lolong_long_long_lo_long_long_lolong_long_long_lo_long_long_lo", }; }

Style edition 2024:

fn main() { let value = if x == "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." { 0 } else { 10 }; let x = Testing { foo: "long_long_long_long_long_long_long_lo_long_long_long_long_long_long__long_long_long_long_long_long_", bar: "long_long_long_long_long_long_long_long_long_long_lo_long_long_lolong_long_long_lo_long_long_lolong_long_long_lo_long_long_lo", }; }

Fixed indentation of generics in impl blocks

Generics in impl items had excessive indentation.

Style edition 2021:

impl< Target: FromEvent<A> + FromEvent<B>, A: Widget2<Ctx = C>, B: Widget2<Ctx = C>, C: for<'a> CtxFamily<'a>, > Widget2 for WidgetEventLifter<Target, A, B> { type Ctx = C; type Event = Vec<Target>; }

Style edition 2024:

impl< Target: FromEvent<A> + FromEvent<B>, A: Widget2<Ctx = C>, B: Widget2<Ctx = C>, C: for<'a> CtxFamily<'a>, > Widget2 for WidgetEventLifter<Target, A, B> { type Ctx = C; type Event = Vec<Target>; }

Use correct indentation when formatting a complex fn

In some cases, a complex fn signature could end up with an unusual indentation that is now fixed.

Style edition 2021:

fn build_sorted_static_get_entry_names( mut entries: Vec<(u8, &'static str)>, ) -> (impl Fn( AlphabeticalTraversal, Box<dyn dirents_sink::Sink<AlphabeticalTraversal>>, ) -> BoxFuture<'static, Result<Box<dyn dirents_sink::Sealed>, Status>> + Send + Sync + 'static) { }

Style edition 2024:

fn build_sorted_static_get_entry_names( mut entries: Vec<(u8, &'static str)>, ) -> ( impl Fn( AlphabeticalTraversal, Box<dyn dirents_sink::Sink<AlphabeticalTraversal>>, ) -> BoxFuture<'static, Result<Box<dyn dirents_sink::Sealed>, Status>> + Send + Sync + 'static ) { }

Avoid extra space in nested tuple indexing expression

Nested tuple indexing expressions would incorrectly include an extra space.

Style edition 2021:

fn main() { let _ = ((1,),).0 .0; }

Style edition 2024:

fn main() { let _ = ((1,),).0.0; }

End return/break/continue inside a block in a match with a semicolon

A return, break, or continue inside a block in a match arm was incorrectly missing a semicolon.

Style edition 2021:

fn foo() { match 0 { 0 => { return AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA } _ => "", }; }

Style edition 2024:

fn foo() { match 0 { 0 => { return AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA; } _ => "", }; }

Long array and slice patterns are now wrapped

Long array and slice patterns were not getting wrapped properly.

Style edition 2021:

fn main() { let [aaaaaaaaaaaaaaaaaaaaaaaaaa, bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, cccccccccccccccccccccccccc, ddddddddddddddddddddddddd] = panic!(); }

Style edition 2024:

fn main() { let [ aaaaaaaaaaaaaaaaaaaaaaaaaa, bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, cccccccccccccccccccccccccc, ddddddddddddddddddddddddd, ] = panic!(); }

Format the last expression-statement as an expression

The last statement in a block which is an expression is now formatted as an expression.

Style edition 2021:

fn main() { let toto = || { if true { 42 } else { 24 } }; { T } }

Style edition 2024:

fn main() { let toto = || { if true { 42 } else { 24 } }; { T } }

Same formatting between function and macro calls

Some formatting is now the same in a macro invocation as it is in a function call.

Style edition 2021:

fn main() { macro_call!(HAYSTACK .par_iter() .find_any(|&&x| x[0] % 1000 == 999) .is_some()); fn_call( HAYSTACK .par_iter() .find_any(|&&x| x[0] % 1000 == 999) .is_some(), ); }

Style edition 2024:

fn main() { macro_call!( HAYSTACK .par_iter() .find_any(|&&x| x[0] % 1000 == 999) .is_some() ); fn_call( HAYSTACK .par_iter() .find_any(|&&x| x[0] % 1000 == 999) .is_some(), ); }

Force block closures for closures with a single loop body

Closures with a single loop are now formatted as a block expression.

Style edition 2021:

fn main() { thread::spawn(|| loop { println!("iteration"); }); }

Style edition 2024:

fn main() { thread::spawn(|| { loop { println!("iteration"); } }); }

Empty lines in where clauses are now removed

Empty lines in a where clause are now removed.

Style edition 2021:

fn foo<T>(_: T) where T: std::fmt::Debug, T: std::fmt::Display, { }

Style edition 2024:

fn foo<T>(_: T) where T: std::fmt::Debug, T: std::fmt::Display, { }

Fixed formatting of a let-else statement with an attribute

If a let-else statement had an attribute, then it would cause the else clause to incorrectly wrap the else part separately.

Style edition 2021:

fn main() { #[cfg(target_os = "linux")] let x = 42 else { todo!() }; // This is the same without an attribute. let x = 42 else { todo!() }; }

Style edition 2024:

fn main() { #[cfg(target_os = "linux")] let x = 42 else { todo!() }; // This is the same without an attribute. let x = 42 else { todo!() }; }

Off-by-one error for wrapping enum variant doc comments

When using the wrap_comments feature, the comments were being wrapped at a column width off-by-one.

Original:

pub enum Severity { /// But here, this comment is 120 columns wide and the formatter wants to split it up onto two separate lines still. Error, /// This comment is 119 columns wide and works perfectly. Lorem ipsum. lorem ipsum. lorem ipsum. lorem ipsum lorem. Warning, }

Style edition 2021:

pub enum Severity { /// But here, this comment is 120 columns wide and the formatter wants to split it up onto two separate lines /// still. Error, /// This comment is 119 columns wide and works perfectly. Lorem ipsum. lorem ipsum. lorem ipsum. lorem ipsum lorem. Warning, }

Style edition 2024:

pub enum Severity { /// But here, this comment is 120 columns wide and the formatter wants to split it up onto two separate lines still. Error, /// This comment is 119 columns wide and works perfectly. Lorem ipsum. lorem ipsum. lorem ipsum. lorem ipsum lorem. Warning, }

Off-by-one error for format_macro_matchers

When using the format_macro_matchers feature, the matcher was being wrapped at a column width off-by-one.

Style edition 2021:

macro_rules! test { ($aasdfghj:expr, $qwertyuiop:expr, $zxcvbnmasdfghjkl:expr, $aeiouaeiouaeio:expr, $add:expr) => {{ return; }}; }

Style edition 2024:

macro_rules! test { ( $aasdfghj:expr, $qwertyuiop:expr, $zxcvbnmasdfghjkl:expr, $aeiouaeiouaeio:expr, $add:expr ) => {{ return; }}; }

Fixed failure with => in comment after match =>

In certain circumstances if a comment contained a => after the => in a match expression, this would cause a failure to format correctly.

Style edition 2021:

fn main() { match a { _ => // comment with => { println!("A") } } }

Style edition 2024:

fn main() { match a { _ => // comment with => { println!("A") } } }

Multiple inner attributes in a match expression indented incorrectly

Multiple inner attributes in a match expression were being indented incorrectly.

Style edition 2021:

pub fn main() { match a { #![attr1] #![attr2] _ => None, } }

Style edition 2024:

pub fn main() { match a { #![attr1] #![attr2] _ => None, } }

Migration

The change can be applied automatically by running cargo fmt or rustfmt with the 2024 Edition. See the Style edition chapter for more information on migrating and how style editions work.

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

Rustfmt: Raw identifier sorting

Summary

rustfmt now properly sorts raw identifiers.

Details

The Rust Style Guide includes rules for sorting that rustfmt applies in various contexts, such as on imports.

Prior to the 2024 Edition, when sorting rustfmt would use the leading r# token instead of the ident which led to unwanted results. For example:

use websocket::client::ClientBuilder; use websocket::r#async::futures::Stream; use websocket::result::WebSocketError;

In the 2024 Edition, rustfmt now produces:

use websocket::r#async::futures::Stream; use websocket::client::ClientBuilder; use websocket::result::WebSocketError;

Migration

The change can be applied automatically by running cargo fmt or rustfmt with the 2024 Edition. See the Style edition chapter for more information on migrating and how style editions work.

Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。

Rustfmt: Version sorting

Summary

rustfmt utilizes a new sorting algorithm.

Details

The Rust Style Guide includes rules for sorting that rustfmt applies in various contexts, such as on imports.

Previous versions of the Style Guide and Rustfmt generally used an "ASCIIbetical" based approach. In the 2024 Edition this is changed to use a version-sort like algorithm that compares Unicode characters lexicographically and provides better results in ASCII digit comparisons.

For example with a given (unsorted) input:

use std::num::{NonZeroU32, NonZeroU16, NonZeroU8, NonZeroU64}; use std::io::{Write, Read, stdout, self};

In the prior Editions, rustfmt would have produced:

use std::io::{self, stdout, Read, Write}; use std::num::{NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8};

In the 2024 Edition, rustfmt now produces:

use std::io::{self, Read, Write, stdout}; use std::num::{NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64};

Migration

The change can be applied automatically by running cargo fmt or rustfmt with the 2024 Edition. See the Style edition chapter for more information on migrating and how style editions work.