あずんひの日

あずんひの色々を書き留めるブログ

Rust 1.68を早めに深掘り

本日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;

現在のプラットフォームにおける、パス要素の主な区切り文字。

例えばUnixでは/Windowsでは\である。

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セクション(※訳注:英語ページ)を参照。

変更点リスト

言語

コンパイラ

ライブラリ

安定化されたAPI

以下のAPIが定数文脈で使えるようになった。

Cargo

互換性メモ

内部の変更

これらの変更がユーザーに直接利益をもたらすわけではないものの、コンパイラ及び周辺ツール内部では重要なパフォーマンス改善をもたらす。

関連リンク

さいごに

次のリリースのRust 1.69はあずんひの誕生日である4/21(金)にリリースされる予定です。 Rust 1.69ではめぼしい新機能はなさそうです(´・ω・`)

ライセンス表記

  • この記事はApache 2/MITのデュアルライセンスで公開されている公式リリースノート及びドキュメントから翻訳・追記をしています
  • 冒頭の画像中にはRust公式サイトで配布されているロゴを使用しており、 このロゴはRust財団によってCC-BYの下で配布されています
  • 冒頭の画像はいらすとやさんの画像を使っています。いつもありがとうございます