序文
Rust エディションガイドへようこそ! 「エディション」とは、Rust に後方互換性が保てなくなるようなアップデートを行うための方法です。
このガイドでは、下記の項目について説明します:
- エディションとは何か
- 各エディションの変更内容
- コードをあるエディションから別のエディションへ移行する方法
エディションとは?
Rust 1.0 のリリースでは、Rust のコア機能として「よどみない安定性」が提供されるようになりました。 Rust は、1.0 のリリース以来、いちど安定版にリリースされた機能は、将来の全リリースに渡ってサポートし続ける、というルールの下で開発されてきました。
一方で、後方互換でないような小さい変更を言語に加えることも、ときには便利です。
最もわかりやすいのは新しいキーワードの導入で、これは同名の変数を使えなくします。
例えば、Rust の最初のバージョンには async
や await
といったキーワードはありませんでした。
後のバージョンになってこれらを突然キーワードに変えてしまうと、例えば 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 には、プロジェクトのエディションを進めるための自動移行ツールが付属しています。 このツールは、あなたのソースコードを書き換えて次のエディションに適合させます。 簡単にいうと、新しいエディションに進むためには次のようにすればよいです。
cargo fix --edition
を実行するCargo.toml
のedition
フィールドを新しいエディションに設定する。たとえば、edition = "2021"
とするcargo build
やcargo test
を実行して、修正がうまくいったことを検証する。
以下のセクションで、これらの手順の詳細と、その途中で起こりうる問題点について詳しく説明します。
我々は、新しいエディションへの移行をできるだけスムーズに行えるようにしたいと考えています。 もし、最新のエディションにアップグレードするのが大変な場合は、我々はそれをバグとみなします。 もし移行時に問題があった場合にはバグ登録してください。 よろしくお願いします!
訳注:Rustの日本語コミュニティもあります。 Slackを使用しておりこちらから登録できます。
移行の開始
例えば、2015エディションから2018エディションに移行する場合を見てみましょう。 ここで説明する手順は、例えば2021エディションのように、別のエディションに移行する場合も実質的に同様です。
src/lib.rs
に以下のコードがあるクレートがあるとします。
このコードは i32
という無名パラメータを使用しています。
これは Rust 2018ではサポートされておらず、コンパイルに失敗します。
このコードを更新してみましょう。
あなたのコードを新しいエディションでコンパイルできるようにする
あなたのコードは新しいエディションに互換性のない機能を使っているかもしれないし、使っていないかもしれません。
Cargo には cargo fix
というサブコマンドがあり、これがあなたのコードを自動的に更新して次のエディションへの移行を補助してくれます。
まず初めに、これを実行してみましょう。
cargo fix --edition
これはあなたのコードをチェックして、自動的に移行の問題を修正してくれます。
もう一度 src/lib.rs
を見てみましょう。
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.rs
や src/main.rs
の先頭)に #![warn(keyword_idents)]
を追加して、cargo fix
を実行すれば、そのリントによる提案だけを受け入れることができます。
各エディションで有効化されるリントの一覧は、リントグループのページを見るか、 rustc -Whelp
コマンドを実行すれば確認できます。
マクロの移行
マクロの中には、エディションを進めるにあたって手作業が必要なものがあります。
例えば、cargo fix --edition
は、次のエディションで動作しない文法を生成するマクロを自動修正することは難しいかもしれません。
これは、手続き型マクロと macro_rules
を使ったマクロの双方で問題になります。
macro_rules
を使ったマクロは、マクロが同じクレートに属していたら自動でアップデートできる場合もありますが、いくつかの状況ではできません。
手続き型マクロは原則、全く修正できないと言っていいでしょう。
例えば、この(わざとらしい)マクロ foo
を含むクレートを 2015 から 2018 に移行しようとしても、foo
は自動修復されません。
マクロが 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 Code で Rust Analyzer 拡張 を使えば、自動的に提案を受け入れるための「クイックフィックス」が使えます。 他にも多くのエディタで同様の機能が使えます。
rustfix
ライブラリを用いて、移行ツールを自作する。 このライブラリは Cargo 内部でも使われており、コンパイラからの JSON メッセージを元にソースコードを編集します。 ライブラリの使用例は、examples
ディレクトリをご覧ください。
新しいエディションで慣用的な書き方をする
エディションは、新機能の追加や古い機能の削除のためだけのものではありません。 どんなプログラミング言語でも、その言語らしい書き方は時代によって変化します。Rust も同じです。 古いプログラムはコンパイルには通るかもしれませんが、今は別の書き方があるかもしれません。
例えば、Rust 2015 では、外部クレートは以下のように extern crate
で明示的に宣言される必要がありました:
// src/lib.rs
extern crate rand;
Rust 2018 では、外部クレートを使うのにextern crate
は必要ありません。
cargo fix
には --edition-idioms
オプションがあり、古い書き方の一部を新しい書き方に書き換えることができます。
警告: 現行の「イディオムリント」にはいくつか問題があることが知られています。 これらのリントはときどき、受け入れるとコンパイルできなくなるような誤った提案をすることがあります。 現在、以下のリントがあります。
- Edition 2018:
- Edition 2021 にイディオムリントはありません。
以下の手順は、コンパイラや 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
Rust 2018 のリリースのために、エディションシステムが作られました。 Rust 2018 のリリースは、生産性をテーマに掲げた数々の新機能とともにもたらされました。 ほとんどの新機能には後方互換性があり、すべてのエディションで使用可能となりました。 一方、一部の変更にはエディション機構が必要となりました(代表例はモジュールシステムの変更です)。
パスとモジュールシステムへの変更
概要
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.rs
とfoo/
サブディレクトリは共存できます。サブディレクトリにサブモジュールを置く場合でも、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.rs
か foo/mod.rs
のどちらにも書けました。
もしサブモジュールがある場合、必ず foo/mod.rs
に書かなくてはなりませんでした。
したがって、foo
のサブモジュール bar
は、foo/bar.rs
に書かれることになりました。
Rust 2018 では、サブモジュールのあるモジュールは mod.rs
に書かなくてはならないという制限はなくなりました。
foo.rs
は foo.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 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
と書いてローカルのモジュールやアイテムであることを明示すればよいです。
トレイト関数の匿名パラメータの非推奨化
概要
- 関数に本体があるとき、トレイト関数のパラメータは、任意の論駁不可能なパターンを使えます。
詳細
RFC #1685 に基づいて、トレイト関数のパラメータを匿名にすることはできなくなりました。
例えば、2015 エディションでは、以下のように書けました:
2018 エディションでは、すべての引数に(ただの _
であってもいいので、何らかの)名前がついていなければなりません:
新しいキーワード
概要
動機
トレイトオブジェクトを表す dyn Trait
dyn Trait
機能は、トレイトオブジェクトを使うための新しい構文です。簡単に言うと:
Box<Trait>
はBox<dyn Trait>
になり、&Trait
と&mut Trait
は&dyn Trait
と&mut dyn Trait
になる
といった具合です。プログラム内では:
これだけです!
なぜ?
トレイトオブジェクトにトレイト名をそのまま使うのは悪手だったと、後になって分かりました。 今までの構文は、経験者にとってさえ往々にして曖昧にして難解で、代替機能を使うべきで本来お呼びでないような場面[^1]で頻繁に使われ、時には遅く、代替機能にはできることができないのです。
その上、impl Trait
が入ったことで、「impl Trait
か dyn Trait
か」の関係はより対称的になり、「impl Trait
か Trait
か」よりちょっといい感じです。
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
構文を使用することが必要になりました。
async
と await
これらのキーワードは Rust に非同期の機能を実装するために予約されました。非同期の機能は最終的に 1.39.0 でリリースされました。
キーワード try
キーワード try
は try
ブロックで使うために予約されましたが、(これを書いている時点で)まだ安定化されていません(追跡イシュー)
推論変数への生ポインタに対するメソッドのディスパッチ
概要
- リント
tyvar_behind_raw_pointer
はハードエラー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 T
はis_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 エディションでハードエラーに格上げされました。これが、本ページで説明されている変更点です。
これらのソースコードは mikeyhew 氏による rust-lang/rust#46906 へのコメントより引用されたものです。ただし、コメントが著者によって追加されています。
関連関数のうち、第一引数が 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 では、これに加えて path
が src/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()
という呼び出しは、
std
の TryInto
もインポートされているときは、動かなくなる場合があります。
なぜなら、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
の値に対してメソッドを呼び出すときに、メソッド名が新しくプレリュードに追加されたトレイトと重複していることがあります:
静的ディスパッチのときと違って、トレイトオブジェクトに対してトレイトメソッドを呼び出すときは、そのトレイトがスコープ内にある必要はありません。
TryInto
トレイトがスコープ内にあるときは (Rust 2021 ではそうなのですが)、曖昧性が発生します。
MyTrait::try_into
と std::convert::TryInto::try_into
のどちらが呼び出されるべきなのでしょうか?
この場合、さらなる参照外しをするか、もしくはメソッドレシーバーの型を明示することで修正できます。
これにより、dyn Trait
のメソッドとプレリュードのトレイトのメソッドのどちらが選ばれているかが明確になります。
たとえば、上の f.try_into()
を (&*f).try_into()
にすると、try_into
が dyn Trait
に対して呼び出されることがはっきりします。
これに該当するのはMyTrait::try_into
メソッドのみです。
移行が不要な場合
固有メソッド
トレイトメソッドと同名の固有メソッドを定義しているような型もたくさんあります。
たとえば、以下では MyStruct
が from_iter
を実装していますが、
これは標準ライブラリの FromIterator
トレイトのメソッドと同名です。
固有メソッドは常にトレイトメソッドより優先されるため、移行作業の必要はありません。
実装の参考事項
2021 エディションを導入することで名前解決に衝突が生じるかどうか(すなわち、エディションを変えることでコードが壊れるかどうか)を判断するために、このリントはいくつかの要素を考慮する必要があります。たとえば以下のような点です:
- 完全修飾呼び出しとドット呼び出しメソッド構文のどちらが使われているか?
- これは、メソッド呼び出し構文の自動参照付けと自動参照外しによる名前の解決方法に影響します。ドット呼び出しメソッド構文では、手動で参照外し/参照付けすることで優先順位を決められますが、完全修飾呼び出しではメソッドパス中に型とトレイト名が指定されていなければなりません (例:
<Type as Trait>::method
)
- これは、メソッド呼び出し構文の自動参照付けと自動参照外しによる名前の解決方法に影響します。ドット呼び出しメソッド構文では、手動で参照外し/参照付けすることで優先順位を決められますが、完全修飾呼び出しではメソッドパス中に型とトレイト名が指定されていなければなりません (例:
- 固有メソッドとトレイトメソッドのどちらが呼び出されているか?
- 固有メソッドはトレイトメソッドより優先されるので、
self
を取るトレイトメソッドは、TryInto::try_into
より優先されますが、&self
や&mut self
をとる固有メソッドは、自動参照付けが必要なので優先されません(もっとも、TryInto
はself
を取るので、それは当てはまりませんが)
- 固有メソッドはトレイトメソッドより優先されるので、
- そのメソッドは
core
かstd
から来たものか? (トレイトは自分自身とは衝突しないので) - その型は、名前が衝突するようなトレイトを実装しているか?
- メソッドが動的ディスパッチによって呼び出されているか? (つまり、
self
の型がdyn Trait
か?)- その場合、トレイトのインポートは名前解決に影響しないので、移行リントを出す必要はありません
デフォルトの Cargo のフィーチャリゾルバ
概要
edition = "2021"
ではCargo.toml
でresolver = "2"
が設定されているとみなされます。
詳細
Rust 1.51.0 から、Cargo には新しいフィーチャリゾルバがオプトインできるようになっています。
これは、Cargo.toml
で resolver = "2"
と書くことで有効化できます。
Rust 2021 から、これがデフォルトになりました。
つまり、 Cargo.toml
に edition = "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"
我々のパッケージでは、今までは bstr
の words_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 などの) ターゲットをビルドするときに必要でない限り、フィーチャは有効化されません。
実際の例としては、diesel
と diesel_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" (*)
この出力例からは、foo
が bar
に "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 + 1
がa
でなくa.x
だけをキャプチャするようになりました。- これにより、ドロップのタイミングが変わったり、クロージャが
Send
やClone
を実装するかどうかが変わったりします。cargo fix
は、このような違いが起こりうると検出した場合、let _ = &a
のような文を挿入して、クロージャが変数全体をキャプチャするように強制します。
詳細
クロージャは、本体の中で使用しているすべてのものを自動的にキャプチャします。
例えば、|| a + 1
と書くと、周囲のコンテキスト中の a
への参照が自動的にキャプチャされます。
Rust 2018 以前では、クロージャに使われているのが1つのフィールドだけであっても、クロージャは変数全体をキャプチャします。
例えば、 || a.x + 1
は a.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」をクロージャの中に挿入して、強制的に全ての変数がキャプチャされるようにします:
この解析は保守的です。ほとんどの場合、ダミーの let は問題なく消すことができ、消してもプログラムはきちんと動きます。
ワイルドカードパターン
クロージャは本当に読む必要のあるデータだけをキャプチャするようになったので、次のコードは x
をキャプチャしません:
この let _ = x
は何もしません。
なぜなら、_
パターンは右辺を無視し、さらに、x
はメモリ上のある場所(この場合は変数)への参照だからです。
この変更(いくつかの値がキャプチャされなくなること)そのものによってコード変更の提案がなされることはありませんが、後で説明する「ドロップ順序」の変更と組み合わせると、提案がなされる場合もあります。
ちなみに: 似たような式の中には、同じく自動挿入される "ダミーの let" であっても、let _ = &x
のように「何もしない」わけではない文もあります。なぜかというと、右辺(&x
)はメモリ上のある場所を指し示すのではなく、値が評価されるべき式となるからです(その評価結果は捨てられますが)。
ドロップの順序
クロージャが変数 t
の値の所有権を取るとき、その値がドロップされるのは t
がスコープ外に出たときではなく、そのクロージャがドロップされたときになります:
上記のコードの挙動は Rust 2018 と Rust 2021 で同じです。ところが、クロージャが変数の一部の所有権を取るとき、違いが発生します:
ほとんどの場合、ドロップのタイミングが変わってもメモリが解放されるタイミングが変わるだけで、さほど問題にはなりません。
しかし、Drop
の実装に副作用のある(いわゆるデストラクタである)場合、ドロップの順序が変わるとプログラムの意味が変わってしまうかもしれません。
その場合は、コンパイラはダミーの let
を挿入して変数全体がキャプチャされるように提案します。
トレイト実装
何がキャプチャされているかによって、クロージャには自動的に以下のトレイトが実装されます:
Clone
: キャプチャされた値がすべてClone
を実装していた場合。Send
,Sync
,UnwindSafe
などの自動トレイト: キャプチャされた値がすべてそのトレイトを実装していた場合。
Rust 2021 では、キャプチャされる値が変わることによって、クロージャが実装するトレイトも変わることがあります。 先ほどの移行リントは、それぞれのクロージャについて、これまで実装されていた自動トレイトが何であるか、そして移行後もそれらが残るかどうかを調べます。 もし今まで実装されていたトレイトが実装されなくなる場合、「ダミーの let」が挿入されます。
例えば、スレッド間で生ポインタを受け渡しする一般的な方法に、ポインタを構造体でラップし、そのラッパー構造体に自動トレイト Send
/Sync
を実装するというものがあります。
thread::spawn
に渡されるクロージャが使うのは、ラッパー構造体のうち特定の変数だけですが、キャプチャされるのはラッパー構造体全体です。
ラッパー構造体は Send
/Sync
なので、コードは安全であるとみなされ、コンパイルは成功します。
フィールドごとのキャプチャが導入されると、キャプチャ内で使用されているフィールドだけがキャプチャされますが、フィールドの中身はもともと Send
/Sync
でなかったのですから、せっかくラッパーを作っても元の木阿弥です。
panic マクロの一貫性
概要
panic!(..)
では常にformat_args!(..)
が使われるようになりました。つまり、println!()
と同じ書き方をすることになります。panic!("{")
と書くことはできなくなりました。{
を{{
とエスケープしなくてはなりません。x
が文字列リテラルでないときに、panic!(x)
と書くことはできなくなりました。- 文字列でないペイロード付きでパニックしたい場合、
std::panic::panic_any(x)
を使うようにしてください。 - もしくは、
x
のDisplay
実装を用いて、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#ident
は prefix #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
コード移行を手で行いたいか、行う必要があっても、移行は非常に簡単です。
例えば、次のように定義されたマクロがあったとしましょう:
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:
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
キーワードを使用することが必須になりました。
例えば、以下のコードでは &MyTrait
に dyn
キーワードが含まれていないため、Rust 2021 ではただのリントではなくエラーが発生します:
ellipsis_inclusive_range_patterns
:
閉区間パターン(つまり、終端の値を含む範囲)を表す、非推奨の ...
構文は、Rust 2021 では使えなくなります。
式との統一性のため、..=
を使うことが推奨されていました。
例えば、次のコードはパターンとして ...
を使っているため、Rust 2021 ではただのリントではなくエラーが発生します:
移行
あなたの Rust 2015 か 2018 のコードで、bare_trait_objects
や ellipsis_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
に書き換える必要があります。
例えば以下のようになります。
Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。
C-string literals
Summary
- Literals of the form
c"foo"
orcr"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.
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.
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 byuse<..>
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:
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:
Conversely, this is 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.:
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.:
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.:
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.:
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.:
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:
...into:
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:
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.:
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.:
With the use<..>
bound syntax, the Captures
trick is no longer needed and can be replaced with the following in all editions:
In Rust 2024, the use<..>
bound can often be omitted entirely, and the above can be written simply as:
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.:
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:
In Rust 2024, the use<..>
bound can often be omitted entirely, and the above can be written simply as:
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 theelse
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:
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.
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.
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:
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:
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:
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:
Details are documented at RFC 3606
Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。
Match ergonomics reservations
Summary
- Writing
mut
,ref
, orref 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
, writingmut
,ref
, orref mut
on a binding is an error.
- Put differently, when the default binding mode is not
- 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
.
- Put differently, reference patterns can only match against references in the scrutinee when the default binding mode is
Details
Background
Within match
, let
, and other constructs, we match a pattern against a scrutinee. E.g.:
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:
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:
By contrast:
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:
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:
ref
/ ref mut
restriction
In Rust 2021 and earlier editions, we allow:
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:
Reference patterns restriction
In Rust 2021 and earlier editions, we allow this oddity:
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:
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...
...into this:
Alternatively, you can manually enable the lint to find patterns that will need to be migrated:
Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。
Unsafe extern
blocks
Summary
extern
blocks must now be marked with theunsafe
keyword.
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:
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.
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.
Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。
Unsafe attributes
Summary
- The following attributes must now be marked as
unsafe
:
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:
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.
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:
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.
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.
The solution is to wrap any unsafe operations in an unsafe
block:
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.
Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。
Disallow references to static mut
Summary
- The
static_mut_refs
lint level is nowdeny
by default. This checks for taking a shared or mutable reference to astatic 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.
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:
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
).
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.
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.
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.
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:
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:
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.
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
- Never type (
!
) to any type ("never-to-any") coercions fall back to never type (!
) rather than to unit type (()
). - The
never_type_fallback_flowing_into_unsafe
lint is nowdeny
by default.
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:
This can lead to compilation errors if the type cannot be inferred:
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:
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:
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:
Another relatively common case is panicking in a closure:
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:
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:
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:
Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。
Macro Fragment Specifiers
Summary
- The
expr
fragment specifier now also supportsconst
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.
Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。
Missing macro fragment specifiers
Summary
- The
missing_fragment_specifier
lint is now a hard error.
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
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
gen
is a reserved keyword.
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
:
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:
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:
標準ライブラリ
以降の節では、2024 エディションでの標準ライブラリへのアップデートについて詳説します。
Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。
Changes to the prelude
Summary
- The
Future
andIntoFuture
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:
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 callIntoIterator::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:
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:
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.
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
- The following functions are now marked
unsafe
:
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:
Cargo
以降の節では、2024 エディションでの Cargo へのアップデートについて詳説します。
Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、原文(英語版)をご参照ください。
Cargo: Rust-version aware resolver
Summary
edition = "2024"
impliesresolver = "3"
inCargo.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
; usedefault-features
instead. - Removed
crate_type
; usecrate-type
instead. - Removed
proc_macro
; useproc-macro
instead. - Removed
dev_dependencies
; usedev-dependencies
instead. - Removed
build_dependencies
; usebuild-dependencies
instead.
- Removed
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 specifiesdefault-features = true
(or does not specifydefault-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.
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.
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:
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.:
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 ofLocation
. 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.