本日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_STRimpl 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ではめぼしい新機能はなさそうです(´・ω・`)