本日3/10(金)にリリースされたRust 1.68の変更点を詳しく紹介します。 もしこの記事が参考になれば記事末尾から活動を支援頂けると嬉しいです。
ピックアップ
個人的に注目する変更点を「ピックアップ」としてまとめました。 全ての変更点を網羅したリストは変更点リストをご覧ください。
Cargoでのインデックス更新を高速化できるようになった
Cargoのレジストリ用に疎(sparse)なプロトコルを使用できるようになりました。
これにより、cargo build
などで時々発生するUpdating crates.io index
による待ち時間が減少します。
このプロトコルは既定では有効化されておらず、環境変数CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse
を設定するか、
~/.cargo/config.toml
で以下のように設定することで使用することができます。
恐らく近いうちに既定で有効化されるのではないかと思います。
[registries.crates-io] protocol = 'sparse'
なお、crates.io以外のレジストリでこのプロトコルを使用することはまだできません。
これまではhttps://github.com/rust-lang/crates.io-indexから巨大なgitレポジトリをcloneしていましたが、 疎なプロトコルではhttps://index.crates.io/からファイルを直接、かつ並列にダウンロードするため高速化されるようです。
変数をピン留めするマクロが使えるようになった
変数をスタック等にピン留めしてPin<&mut T>
を生成するstd::pin::pin!
マクロが使えるようになりました。
pin!
マクロを使うときはFuture::poll
などを手動で実装するときなので使うことはないかもしれません。
このマクロは他のマクロと違い既定では使えないため手動でインポートする必要があります。
use std::pin::{pin, Pin}; fn hoge() { // 値を直接ピン留めする let _pinned: Pin<&mut u32> = pin!(42); } async fn fuga() { // 非同期関数の中でもOK let _pinned: Pin<&mut Vec<u32>> = pin!(vec![1, 2, 3]); }
これまでもpin-utilsクレートのpin_utils::pin_mut!
マクロやtokioクレートのtokio::pin!
マクロが近い機能を提供していましたが、
これらは既に定義済みの変数に対してマクロを使用する、あるいは変数定義全体をマクロで囲う必要があるなど使い勝手が良いものではありませんでした。
今回標準ライブラリに導入されたstd::pin::pin!
マクロでは以下の記事にあるようにちょっとした(標準ライブラリだけが使える)ハックが使われており、
使い勝手が向上しています。
安定化されたAPIのドキュメント
安定化されたAPIのドキュメントを独自に訳して紹介します。リストだけ見たい方は安定化されたAPIをご覧ください。
path::MAIN_SEPARATOR_STR
#[stable(feature = "main_separator_str", since = "1.68.0")] pub const MAIN_SEPARATOR_STR: &str = crate::sys::path::MAIN_SEP_STR;
現在のプラットフォームにおける、パス要素の主な区切り文字。
pin::pin!
#[stable(feature = "pin_macro", since = "1.68.0")] #[rustc_macro_transparency = "semitransparent"] #[allow_internal_unstable(unsafe_pin_internals)] pub macro pin($value:expr $(,)?) { /* 実装は省略 */ }
value: T
を局所的※1にピン留め※2し、Pin<&mut T>
を生成する。
Box::pin
とは異なり、これにはヒープ確保を伴わない。
サンプル
基本的な使い方
use core::pin::{pin, Pin}; fn stuff(foo: Pin<&mut Foo>) { // … } let pinned_foo = pin!(Foo { /* … */ }); stuff(pinned_foo); // あるいは直接 stuff(pin!(Foo { /* … */ }));
use core::marker::PhantomPinned as Foo; use core::pin::{pin, Pin}; fn stuff(foo: Pin<&mut Foo>) { // … let _ = foo; } let pinned_foo = pin!(Foo { /* … */ }); stuff(pinned_foo); // あるいは直接 stuff(pin!(Foo { /* … */ }));
Future
を手動で(Unpin
境界なしに)ポーリングする
use std::{ future::Future, pin::pin, task::{Context, Poll}, thread, }; /// Futureが完了するまで実行 fn block_on<Fut: Future>(fut: Fut) -> Fut::Output { let waker_that_unparks_thread = // … let mut cx = Context::from_waker(&waker_that_unparks_thread); // ポーリングできるようFutureをピン留め let mut pinned_fut = pin!(fut); loop { match pinned_fut.as_mut().poll(&mut cx) { Poll::Pending => thread::park(), Poll::Ready(res) => return res, } } }
use std::{ future::Future, pin::pin, task::{Context, Poll}, thread, }; use std::{sync::Arc, task::Wake, thread::Thread}; /// 呼び出された際に現在のスレッドを起床させるwaker struct ThreadWaker(Thread); impl Wake for ThreadWaker { fn wake(self: Arc<Self>) { self.0.unpark(); } } /// Futureが完了するまで実行 fn block_on<Fut: Future>(fut: Fut) -> Fut::Output { let waker_that_unparks_thread = // … Arc::new(ThreadWaker(thread::current())).into(); let mut cx = Context::from_waker(&waker_that_unparks_thread); // ポーリングできるようFutureをピン留め let mut pinned_fut = pin!(fut); loop { match pinned_fut.as_mut().poll(&mut cx) { Poll::Pending => thread::park(), Poll::Ready(res) => return res, } } } assert_eq!(42, block_on(async { 42 }));
Generator
で使う
※訳注:このサンプルはNightlyでのみ動作する。
#![feature(generators, generator_trait)] use core::{ ops::{Generator, GeneratorState}, pin::pin, }; fn generator_fn() -> impl Generator<Yield = usize, Return = ()> /* not Unpin */ { // ローカル変数がyieldをまたげるよう、ジェネレーターを // vvvvvv 自己参照できるようにする(`Unpin`ではない) static || { let foo = String::from("foo"); let foo_ref = &foo; // ------+ yield 0; // | <- yieldをまたぐ println!("{foo_ref}"); // <--+ yield foo.len(); } } fn main() { let mut generator = pin!(generator_fn()); match generator.as_mut().resume(()) { GeneratorState::Yielded(0) => {}, _ => unreachable!(), } match generator.as_mut().resume(()) { GeneratorState::Yielded(3) => {}, _ => unreachable!(), } match generator.resume(()) { GeneratorState::Yielded(_) => unreachable!(), GeneratorState::Complete(()) => {}, } }
備考
正確には値は局所記憶域にピン留めされるため、結果として得られるPin<&mut T>
は
ブロックに紐付いた局所変数を借用することになり、変数はブロックを抜けられない。
例えば下記はコンパイルに失敗する。
use core::pin::{pin, Pin}; let x: Pin<&mut Foo> = { let x: Pin<&mut Foo> = pin!(Foo { /* … */ }); x }; // ← Fooがドロップする stuff(x); // エラー:ドロップした値の使用
use core::pin::{pin, Pin}; use core::{marker::PhantomPinned as Foo, mem::drop as stuff}; let x: Pin<&mut Foo> = { let x: Pin<&mut Foo> = pin!(Foo { /* … */ }); x }; // ← Fooがドロップする stuff(x); // エラー:ドロップした値の使用
エラーメッセージ
error[E0716]: temporary value dropped while borrowed --> src/main.rs:9:28 | 8 | let x: Pin<&mut Foo> = { | - borrow later stored here 9 | let x: Pin<&mut Foo> = pin!(Foo { /* … */ }); | ^^^^^^^^^^^^^^^^^^^^^ creates a temporary value which is freed while still in use 10 | x 11 | }; // ← Fooがドロップする | - temporary value is freed at the end of this statement | = note: consider using a `let` binding to create a longer lived value
このため、pin!
によって値を返すつもりでピン留めするのは不適切である。
その代わり、消費される地点までは値はピン留めしない状態で渡すことが想定されており、
その後pin!
により値を局所的にピン留めすることが有用かつ賢明である。
どうしてもピン留めされた値を返したい場合はBox::pin
の使用を検討されたい。
とは言えpin!
を使ったスタックへのピン留め※1は、
Box::pin
を使用した新しいヒープ領域へのピン留めよりもコストは低くなるものである。
さらにはアロケーターも不要であることから、pin!
はunsafe
でない、#![no_std]
互換な、Pin
の主要なコンストラクタと言える。
※1:これはしばしば「スタック」留め("stack"-pinning)と呼ばれ、
局所変数はほとんど常にスタックに配置される(非async
関数の本体内など)。
しかしasync fn
またはブロックの内部(より一般的にはジェネレーター内部)においては、
.await
地点(yield
地点)をまたぐすべての局所変数は
Future
により(Generator
により)捕捉されその状態の一部となることから、
その変数がどこにあろうと保持されるというのが実態である。
※2:指定された値(の型T
)がUnpin
を実装していない場合、そのvalue
はメモリにピン留めされムーブができなくなる。
そうでない場合、Pin<&mut T>
は&mut T
の様に振る舞い、
mem::replace()
のような操作は値を取り出すこと、つまりムーブすることができる。
詳細はpin
モジュールのUnpin
セクション(※訳注:英語ページ)を参照。
変更点リスト
言語
- default_alloc_error_handlerを安定化。
stable版で
alloc
を使用する際にメモリ確保失敗時の処理を定義する必要がなくなった。 独自に定義することは依然として不安定機能である - 呼び出し規約
efiapi
を安定化 - 解放用自動生成コード(drop glue)のある型における暗黙的な昇格を削除
コンパイラ
bindings_with_variant_name
を既定拒否に変更- ..をletの初期化子としてパースすることを許容
armv7-sony-vita-newlibeabihf
をTier 3ターゲットとして追加- コンパイル時定数評価においてアライメントを常に検査
- 既定では「split dwarf inlining」を無効化
- Fuchsiaのターゲットトリプルにベンダーを追加
- s390x-linux向けにサニタイザーを有効化
ライブラリ
- WeakでのDebugの実装において境界を緩和
std::task::Context
を!Sendかつ!Syncに変更- PhantomDataのレイアウトを保証
OnceWith
とRepeatWith
でDebugを自動導出しない- PathBufにDerefMutを実装
Vec -> VecDeque
変換にO(1)の保証を追加- peek_mut()におけるリークの増幅により、BinaryHeapの不変条件を維持する
※訳注:更なるリークを起こしうる(安全な)リークのことをリークの増幅と呼び、このPRではpeek_mut()
でリークの増幅を使うようにした
安定化されたAPI
{core,std}::pin::pin!
impl From<bool> for {f32,f64}
std::path::MAIN_SEPARATOR_STR
impl DerefMut for PathBuf
以下のAPIが定数文脈で使えるようになった。
Cargo
- crates.io向けの疎なレジストリへの対応を安定化
cargo build --verbose
が再コンパイルの理由を詳しく表示するようになった- オプション
net.git-fetch-with-cli
が有効であってもcrates.ioのインデックスを更新する進捗が表示されるようになった
互換性メモ
SEMICOLON_IN_EXPRESSIONS_FROM_MACROS
を将来的な非互換報告に追加- wasmでの
-Zgcc-ld=lld
は、既定では--target
のみ指定されるようになった IMPLIED_BOUNDS_ENTAILMENT
をDeny + ReportNowに格上げstd::task::Context
がSendとSyncを実装しなくなった
内部の変更
これらの変更がユーザーに直接利益をもたらすわけではないものの、コンパイラ及び周辺ツール内部では重要なパフォーマンス改善をもたらす。
- 囲んでいる項目からの相対的なスパンをエンコード
- AstConvを正規化しない
- 半順序関連のケースで正しい下限境界を探索
- 定数式におけるimplブロックを修正
- Copyの実装において領域を考慮しながら代数的データ型のフィールドを検査
- rustdoc:レーベンシュタイン距離をいじらないようにしてJSの検索ルーチンを簡易化
x86_64-pc-windows-msvc
上のrustcでThinLTOを有効化x86_64-apple-darwin
上のrustcでThinLTOを有効化
関連リンク
さいごに
次のリリースのRust 1.69はあずんひの誕生日である4/21(金)にリリースされる予定です。 Rust 1.69ではめぼしい新機能はなさそうです(´・ω・`)