Rust言語 2021年版の計画(抄訳)

11 min read読了の目安(約10300字 2

はじめに

この記事は、The Plan for the Rust 2021 Editionを基に、ざっくりと訳してみました。

なお、この文書は読みやすいことを前提に翻訳しておりますが、必ずしも正確な翻訳であることを保証するものではありません。予めご了承ください。

詳しくは元の記事を参照してください。

なお、致命的な誤訳やタイポなどがありましたら、コメント欄にてご指摘いただけると幸いです。

ライセンス

この文書のライセンスは原文と同じくMIT および APACHEのデュアルライセンスとします。

https://opensource.org/licenses/MIT

https://opensource.org/licenses/Apache-2.0

謝辞

この翻訳を進めるにあたり、公開場所や翻訳の進め方などについてアドバイスをいただいたRust-jaコミュニティーの tatsuya6502 さんをはじめとする皆さんへ感謝いたします。


Rust言語 2021年版の計画

2021年5月11日 - The Rust 2021 Edition Working Groupを代表してMara Bos氏

このたび、Rust言語の第3版にあたる Rust言語 2021年版が2021年10月にリリースされる運びとなりました。Rust言語 2021年版の仕様変更は小さいものの、Rust言語の使用感が大幅に改善されることが期待されています。

エディション(版)とは?

Rust言語 1.0 のリリースにより、停滞なき安定性がRust言語の最も重要な成果物のひとつとして確立されました。それ以来、Rust言語には、一度安定版でリリースされた機能は、それ以降のすべてのリリース版でもその機能をサポートすることを約束するというルールがあります。

しかし、後方互換性のない言語でも小さな仕様変更を加えることができると、都合が良い場合もあります。もっとも分かりやすいのは、新しいキーワードを導入すると、おなじ名前の変数が無効になるという例です。例えば、Rust言語の最初のバージョンにはasyncawait というキーワードはありませんでした。後のバージョンで、突然これらの単語をキーワードに仕様変更すると、let async = 1;のようなコードが壊れてしまいます。

エディション(版) とは、このような問題を解決するための仕組みです。下位互換性のない機能をリリースしたい場合は、新しいRustの エディション(版) の仕様の一部としてリリースします。エディション(版)はオプトイン方式であるため、既存のクレートは、明示的に新しいエディション(版)に移行するまで、これらの仕様変更を理解することができません。つまり、最新版のRust言語であっても、2018年版以降のエディション(版)を選択しない限り、 async をキーワードとして扱うことはできません。この選択はクレート その Cargo.toml の一部として 行われます。なお、cargo newで作成された新しいクレートは、常に最新の安定版を使用するように設定されます。

[訳注] オプトイン方式とは、事前に許諾を得る仕組みのことです。

エディション(版)はエコシステムを分断するものではありません

エディション(版)に関する最も重要なルールは、あるエディション(版)のクレートが、ほかのエディション(版)でコンパイルされたクレートとスムーズに相互運用できることです。

クレートの相互運用性を求めることは、1つのエディションで行える仕様変更にある程度制限を与えることになります。一般的には、エディション(版)毎の仕様変更は「表面的」なものにとどまります。すべてのRustコードは、エディション(版)に関係なく、最終的には同じ内部表現にコンパイルされます。

エディション(版)の移行は簡単で、ほぼ自動化されています

私たちの目標は、クレートを新しいエディション(版)に簡単にアップグレードできるようにすることです。新しいエディション(版)をリリースする際には自動的に移行するためのツールも提供しています。これは新版と互換性を保つために必要なコードの微修正を行うものです。例えば、Rust言語 2018年版への移行の際にはasyncという名前のものを生識別子の構文 を用いてr#asyncという同等の記述に修正します。

とはいえ、自動的な移行は必ずしも完ぺきではありませんので、手動で修正する必要があるかもしれません。このツールは、コードの正しさや性能に影響を及ぼすような、意味を変えるような修正を避けるように努めています。

Rust言語 2021年版ではどのような仕様変更が予定されていますか?

この数ヶ月間、Rust言語 2021年版ワーキンググループは、この新しいエディション(版)に盛り込むべき内容について、多くの提案を行ってきました。このたび、最終的な仕様の変更点の一覧を発表できることを嬉しく思います。この一覧に掲載されるためには、それぞれの機能が次に挙げる2つの基準を満たす必要がありました。まず、それぞれの機能を担当するRust言語の開発チームによる承認が得られていること。次に、予定されているマイルストーンに間に合うように完成できると確信できるほど、実装が進んでいることです。

prelude への追加

標準ライブラリのprelude は、全モジュールに自動的に取り込まれるものをすべて含んだモジュールです。Option, Vec, drop, Clone などのよく使われるものが含まれています。

Rust言語のコンパイラは、手動で取り込んだアイテムをpreludeからのアイテムよりも優先し、preludeへの追加によって既存のコードが壊れないようにします。

例えば,exampleという名前のクレートやモジュールにpub struct Option;という記述が含まれている場合,use example::*;とすると,Optionは標準ライブラリのものではなく,exampleのものを優先的に参照するようになります

しかし、prelude に トレイト を追加すると、既存のコードを微妙な方法で壊してしまうことがあります。

例えば、MyTryInto トレイトを用いた x.try_into() の呼び出しは、stdTryInto も取り込まれている場合、stdTryIntoも同じ名前のメソッドを提供している為に、どちらのメソッドを呼び出してよいのか曖昧になってコンパイルに失敗するかもしれません。いまだにTryInto を prelude に追加していないのは、このように壊れてしまうコードがたくさん出てしまうからです。

その解決策として、Rust言語 2021年版では、次の3つの新しい機能を prelude に追加しました。

Cargoによるデフォルトの機能リゾルバ

Rust言語 1.51.0以降、Cargoは新機能リゾルバをオプトイン方式でサポートしています。これは Cargo.tomlresolver = "2" で有効にすることができます。

Rust言語 2021年版から、これがデフォルトになります。すなわち、Cargo.tomledition = "2021" と記述すると、resolver = "2" を指定したのと同じ意味になります。

新機能リゾルバでは、複数の方法で依存しているクレートに対して要求された機能をすべてマージしなくなりました。
詳細については the announcement of Rust 1.51 を参照してください。

配列用の IntoIterator

Rust言語 1.53までは、配列への参照のみが IntoIterator を実装できました。
つまり、&[1, 2, 3]&mut [1, 2, 3] を反復して参照することはできますが、[1, 2, 3] を直接反復して参照することはできません。

for &e in &[1, 2, 3] {} // Ok :)

for e in [1, 2, 3] {} // Error :(

これは、長年の課題でしたが、解決するのは容易ではありません。トレイトの実装を追加しただけで既存のコードが壊れてしまうからです。
例えば、array.into_iter() は、how method call syntax works に則って、暗黙のうちに (&array).into_iter() を呼び出しているので、現状でコンパイルできますが、トレイトの実装を追加してしまうと、意味が変わってしまいます。

通常であれば、この種の破損(トレイトの実装を追加すること)は'minor'と分類し、許容していますが、今回のケースでは、壊れてしまうコードが多岐にわたるため許容することはできません。

Rust言語 2021年版では、配列に対してIntoIteratorのみを実装するという提案も何度となく行われてきました。しかし、事は容易ではありません。というのも、トレイトの実装はあるエディションでは存在するのに、別のエディションでは存在しないという様に混在できないからです。

代わりに、すべてのエディション(Rust 1.53以降)でトレイト実装を追加することにしましたが、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]) といった他の構文には影響を及ぼしません。
それらは、すべてのエディションで動作するようになります。

既存のコードの破壊を防ぐために、巧妙な工夫が必要だったのは残念ですが、この解決策によってエディション間の違いを最小限に抑えることができたことにとても満足しています。
なお、この巧妙な工夫は旧版にのみ存在するものであり、新版で複雑さが増すことはありません。

切り離されたクロージャの捕捉

クロージャ は、本文中から参照するものを自動的に捕捉します。
例えば、|| a + 1は、周囲の文脈からaへの参照を自動的に捕捉します。

今のところ、この仕組みは構造体全体に適用され、一つのフィールドのみを使用する際でも適用されます。
例えば、|| a.x + 1a.xだけでなくaへの参照を捕捉します。
状況によっては、これが問題になる場合があります。
構造体の、あるフィールドがすでに借用されていたり、移動されていたりすると、ほかのフィールドをクロージャで使用することはできません。

let a = SomeStruct::new();

drop(a.x); // 構造体のあるフィールドを移動する

println!("{}", a.y); // Ok: その場合でも、ほかのフィールドは利用できます

let c = || println!("{}", a.y); // Error: c() は`a`全体を捕捉しようと試みます

Rust言語 2021年版からは、クロージャが使用するフィールドのみを捕捉するようになります。そのため、上記の例は、Rust言語 2021年版では問題なくコンパイルできます。

この新しい挙動は、フィールドが削除される順番を変更できることから、新バージョンでのみ有効です。
すべてのエディション(版)の変更については自動的に移行可能で、この問題が発生するクロージャが更新されます。
クロージャ内にlet _ = &a;を挿入すれば、以前のように構造体全体を強制的に捕捉できます。

パニックマクロの整合性

panic!()マクロは、Rust言語の中でも最もよく知られたマクロの一つですが、次のsome subtle surprises という理由から、後方互換性を維持するために変更できないでいました。

panic!("{}", 1); // Ok, パニックメッセージ"1"の表示
panic!("{}"); // Ok, パニックメッセージ"{}"の表示

panic!()マクロは、複数の引数で呼び出されたときにのみ文字列の書式を使用し、単一引数のときは、その引数を無視します。

let a = "{";
println!(a); // Error: 最初の引数は書式文字列定数でなければなりません
panic!(a); // Ok: パニックマクロは引数を無視します

(さらには、panic!(123)といった文字列以外も受け付けますが、一般的ではなく、ほとんど役に立ちません。)

特に、implicit format arguments が安定してくると問題になります。
この機能により、println!("hello {name}")は、println!("hello {}", name)の短縮形となります。ただし、panic!("hello {name}")は、panic!() が単一の引数を書式文字列として処理しないことから、期待通りには動作しません。

このような混乱した状況を避けるために、Rust言語 2021年版では、より整合性のあるpanic!()マクロを取り入れました。新しいpanic!()マクロは、println!() と同様に、任意の式を唯一の引数として受け取ることはなくなり、最初の引数を常に書式文字列として処理します。
panic!()が任意の引数を受け付けなくなったことから、panic_any()が残されたパニックを引き起こす唯一の要因となります。

また、Rust言語 2021年版では、core::panic!()std::panic!() が同一のものとなります。現在、この二つは歴史的経緯の違いから#![no_std]のオンとオフの時で動作が異なります。

予約構文

将来の新しい構文のための名前空間を確保するために、接頭辞付きの識別子と定数のための構文を確保することにしました。
prefix#identifierprefix"string"prefix'c'prefix#123で、prefixには任意の識別子を指定できます。(ただし、b'…' and r"…"などの、すでに意味を持っているものを除きます)

これは、画期的な仕様変更です。マクロが現在、引数としてhello"world"を受け付けるのは、hello"world"の2つの異なるトークンとして認識しているからですが、hello "world"のようにスペースを挿入するだけで、簡単に(自動的に)修正できます。

これらをトークン化エラーとする以外に、the RFC では、どの接頭辞にも意味を持たせていません。
特定の接頭辞に意味を持たせるかどうかは将来の提案に委ねられていますが、現在これらの接頭辞を予約しているおかげで、破壊的な変更にはなりません。

今後、目にするかもしれない新しい接頭辞は次の通りです:

  • f"" は、書式文字列の短縮形です。
    例えば、f"hello {name}"は同等のformat_args!()の呼び出しの短縮形として用いられます。

  • c""z"" は、C言語形式(文字列終端が0)の文字列です。

  • k#keyword は、現行エディション(版)には存在していないキーワードを記述できるようにします。
    例えば、2015年版ではasyncキーワードは存在していませんが、この接頭辞があれば、2015年版ではk#asyncを代替案として受け入れ、2018年版ではasyncをキーワードとして予約されるのを待つことが可能でした。

2つの警告をハードエラーへ昇格

Rust言語 2021年版では、既存の2つのリント(静的解析結果)がハードエラーとなっています。
これらのリント(静的解析結果)は、旧版では警告のままです。

  • bare-trait-objects:
    trait objectsを識別するためにdynキーワードを用いることが、Rust言語 2021年版では必須となります。

  • ellipsis-inclusive-range-patterns:
    包括的な範囲パターンdeprecated ... syntaxは、Rust言語 2021年版では受け付けなくなりました。
    これは、..=に取って代わられましたが、式と整合性があります。

マクロルールのパターン

Rust言語 1.53.0から、patternsが拡張され、パターン内の任意の場所にネストされた|をサポートするようになりました。これにより、Some(1) | Some(2)Some(1 | 2)と記述できるようになりました。
これまで、単に許されていなかっただけですので、画期的な変更とはいえません。

ただし、この仕様変更はmacro_rules macrosにも影響します。
このようなマクロは、:pat フラグメント指定子を使ったパターンを受け入れることができます。現在、:pat| にはマッチしません。Rust 1.53 以前では、すべてのパターン (すべてのネストしたレベル) に | が含まれるわけではなかったからです。
A | Bのようなパターンを受け付けるマクロ、例えば matches!() では $($_:pat)|+ のようなものを使います。Rust言語 1.53.0 では、既存のマクロを壊さないように :pat の意味を変更せず、| を含めるようにしました。

Rust言語 2021年版の一部としてこの仕様変更を行う予定です。
新版では、:patフラグメント指定子は、A | Bマッチします

|を含まない、単一のパターン変数にマッチさせたい場合もあるので、互換性を保つために:pat_paramというフラグメントが追加されました。
この名前は、主な使用例であるクロージャのパラメータ内のパターンを意味しています。

今後の展開は?

計画では、9月までにこれらの仕様変更をマージしてテストを終え、2021年版がRust言語 1.56.0に搭載される予定です。
Rust言語 1.56.0は、その後6週間のベータ版を経て、10月21日にリリース予定です。

しかし、Rust言語はボランティアによって運営されているプロジェクトであることに留意してください。我々は、Rust言語に携わるすべての人の個人的な幸福を、自らが定めた期限や
期待より優先します
そのため、必要に応じてバージョンアップを遅らせたり、期限に間に合わない機能を削除することもあります。

とはいうものの、Rust言語 2021年版に貢献してくれたすべての人々のおかげで、開発は順調で、すでに、難しい問題の多くは解決済みです!💛


7月中には新版についての発表があると思います。その時点で、すべての仕様変更と自動移行が実装され、公開テストの準備が整うことを期待しています。

提案が採用される過程と詳細については、近日中に「Inside Rust」ブログへ掲載予定です。

この記事に贈られたバッジ