以下の内容はhttps://aznhe21.hatenablog.com/entry/2024/09/06/rust-1.81より取得しました。


Rust 1.81を早めに深掘り

本日9/6(金)にリリースされたRust 1.81の変更点を詳しく紹介します。 もしこの記事が参考になれば記事末尾から活動を支援頂けると嬉しいです。

ピックアップ

個人的に注目する変更点を「ピックアップ」としてまとめました。 全ての変更点を網羅したリストは変更点リストをご覧ください。

no_std環境でもErrorトレイトが使えるようになった

これまでErrorトレイトはlibstd環境でのみ使えるものでしたが、Rust 1.81からは#![no_std]環境でも使えるようになりました。

組み込み環境でもエラーを扱いやすくなり、ライブラリでもlibstd環境とそれ以外で実装を切り替えずに済むようになります。嬉しいですね。

警告等が発生することを強制させられるようになった

式や文で警告やエラーなどのリントが発生することを強制させることができる#[expect]属性が使えるようになりました。

使い方は#[allow]#[deny]と同じで、リントの名前を指定します。 #[allow]はリントが発生しても警告・エラーが出ないようにするものですが、 #[expect]はリントが発生しないとコンパイルエラーになります。

個人的には最近tokio::spawnに指定するタスクが終了しないことを明示するために使おうとしたことがあります。 rust-lang/rust#107953で紹介されているstatic_unreachable!()マクロで使われています。

macro_rules! static_unreachable {
    () => {
        #[deny(unfulfilled_lint_expectations)]
        #[expect(unreachable_code)]
        { unreachable!() }
    };
}

tokio::spawn(async {
    loop {
        // 適当な処理
        // break;
    }

    // ループがbreakすると次のエラーが出る
    // error: this lint expectation is unfulfilled
    static_unreachable!() // セミコロンは付けない
});

ソートの実装が切り替わった

これまでは安定ソートにティムソート由来のアルゴリズムが、不安定ソートに[pdqsort]由来のアルゴリズムが使われていました。 Rust 1.81からは安定ソートはdriftsort由来に、不安定ソートはipnsort由来に切り替わりました。

置き換えのPRによると、実行時間とコンパイル時間、そしてバイナリサイズのバランスと取ったものだそうです。

なお、Ordが正しく全順序を実装していない、つまりa.cmp(b)b.cmp(a)の結果が異なる場合にパニックする可能性があるため注意してください。

core::panic::PanicInfostd::panic::PanicInfoの互換性がなくなった

std::panic::PanicInfostd::panic::PanicHookInfoの別名となり、core::panic::PanicInfoとは互換性がなくなりました。

core::panic::PanicInfo#[panic_handler]に渡されるもので、内部のパニック文言は形式化済みの文字列です。 一方std::panic::PanicInfostd::panic::set_hookで設定する関数に渡されるもので、 panic_anyにより文字列以外にも任意の値が入る可能性があります。 この2つは同じ役割のようでいて異なる性質を持っていたため、今回分離されることとなりました。

std::panic::PanicInfoを使っている場合は今後警告が出るようになるため置き換えが必要です。

extern "C"関数で未捕捉のパニックがプログラムを強制終了させるようになった

Rust 1.71ではパニックした際に呼び出し元に巻き戻しが伝播するextern "C-unwind"が追加されましたが、 extern "C"関数でパニックした際の挙動は未定義でした。

Rust 1.81からはextern "C"関数で捕捉されなかったパニックがある場合、プログラムが強制終了するようになりました。 extern "C"関数は慎重に使い、必要に応じてextern "C-unwind"へ置き換えるようにしましょう。

安定化されたAPIのドキュメント

安定化されたAPIのドキュメントを独自に訳して紹介します。リストだけ見たい方は安定化されたAPIをご覧ください。

core::error

原典

めちゃくちゃ長いので割愛

hint::assert_unchecked

原典

#[track_caller]
#[inline(always)]
#[doc(alias = "assume")]
#[stable(feature = "hint_assert_unchecked", since = "1.81.0")]
#[rustc_const_stable(feature = "hint_assert_unchecked", since = "1.81.0")]
pub const unsafe fn assert_unchecked(cond: bool)
{ /* 実装は省略 */ }

condが成立するという健全性コンパイラに約束させる。

これにより最適化の仕事が楽になるかもしれないが、 生成されるコードが遅くなる可能性もある。いずれにせよ、この関数を呼び出すとコンパイルが長くなる可能性が高い。

他ではllvm.assumeや Cの__builtin_assumeで見たことがあるかもしれない。

これは正確性要件を健全性要件に昇格させるものである。よほどの理由がない限り、このようなことはすべきではない。

使い方

これは特定の状況下で使われる局所最適化のための手法であり、関数は何もしなくても良い。 使う場合はその価値を示す再現可能なベンチマークを書き、最適化がより 賢くなり不要になった際には関数の使用を削除することを想定すべきである。

条件が複雑になるほど、この関数が有効である可能性は低くなる。例えば、 assert_unchecked(foo.is_sorted())コンパイラが活用できる可能性がない程度には複雑である。

また基本的な特性を示す必要もない。例えば、コンパイラcount_onesの値域を知っているため、 let n = u32::count_ones(x); assert_unchecked(n <= u32::BITS);としてもメリットはない。

assert_uncheckedは論理的にはif !cond { unreachable_unchecked(); }と同等である。 assert_unchecked(false)と書きたくなったら、直接unreachable_unchecked()と書くべきである。

安全性

condtrueでなくてはならない。この関数をfalseで呼び出すと即座に未定義動作を引き起こす。

サンプル

use core::hint;

/// # 安全性
///
/// `p`は非NULLで有効でなければならない
pub unsafe fn next_value(p: *const i32) -> i32 {
    // 安全性:呼び出し側の不変条件により`p`がNULLでないことは保証される
    unsafe { hint::assert_unchecked(!p.is_null()) }

    if p.is_null() {
        return -1;
    } else {
        // 安全性:呼び出し側の不変条件により`p`が有効であることは保証される
        unsafe { *p + 1 }
    }
}

assert_unchecked抜きの上記関数では最適化ありでも次のコードを生成する

next_value:
        test    rdi, rdi
        je      .LBB0_1
        mov     eax, dword ptr [rdi]
        inc     eax
        ret
.LBB0_1:
        mov     eax, -1
        ret

assertを追加すると最適化での無駄な検査を省くことができる

next_value:
        mov     eax, dword ptr [rdi]
        inc     eax
        ret

この例は実際に使用されるものとは大きく異なる。検査と同じassertをすぐ隣に書くことは冗長であり、 ポインタの逆参照には非NULLという大前提がある。ただし挙動の関連性がそれほど明確でない場合でも、 最適化がどのような変更を加えられるかを示している。

fs::exists

原典

#[stable(feature = "fs_try_exists", since = "1.81.0")]
#[inline]
pub fn exists<P: AsRef<Path>>(path: P) -> io::Result<bool>
    { /* 実装は省略 */ }
}

パスが存在する実体を示す場合にOk(true)を返す。

この関数は宛先ファイルについて情報を問い合わせるためにシンボリックリンクを辿る。 壊れたシンボリックリンクの場合はOk(false)を返す。

Path::existsメソッドとは異なり、この関数はパスが存在するかしないかを検証した場合のみOk(true)またはOk(false)を返す。 存在を確認できない場合や拒否された場合はErr(_)が伝播される。例えば親ディレクトリの1つで一覧する権限が拒否されている場合などが該当する。

この関数はexists()メソッドのいくつかの落とし穴を避けるが、TOCTOUバグ(※訳注:検査時から使用時までの時間差によって生じるバグ)を防げるわけではない。 この関数はTOCTOUバグが問題にならないシナリオでのみ使用すべきである。

サンプル

use std::fs;

assert!(!fs::exists("存在しない.txt").expect("存在しない.txtの存在を検査できなかった"));
assert!(fs::exists("/root/ひみつ.txt").is_err());

AtomicBool::fetch_not

原典

#[cfg(target_has_atomic_load_store = "8")]
impl AtomicBool {
    #[inline]
    #[stable(feature = "atomic_bool_fetch_not", since = "1.81.0")]
    #[cfg(target_has_atomic = "8")]
    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
    pub fn fetch_not(&self, order: Ordering) -> bool
    { /* 実装は省略 */ }
}

真偽値に対して論理否定を行う。

現在の値に対して論理否定を行い、新しい値を設定する。

古い値を返す。

fetch_notはこの操作のメモリ順序を表すOrdering引数を取る。 全ての順序モードが使用可能である。Acquireを使用するとこの操作のストア部分がRelaxedになり、 Releaseを使用するとロード部分がRelaxedになる。

注記:このメソッドはu8上での原子操作に対応するプラットフォームでのみ利用可能である。

サンプル

use std::sync::atomic::{AtomicBool, Ordering};

let hoge = AtomicBool::new(true);
assert_eq!(hoge.fetch_not(Ordering::SeqCst), true);
assert_eq!(hoge.load(Ordering::SeqCst), false);

let hoge = AtomicBool::new(false);
assert_eq!(hoge.fetch_not(Ordering::SeqCst), false);
assert_eq!(hoge.load(Ordering::SeqCst), true);

Duration::abs_diff

原典

impl Duration {
    #[stable(feature = "duration_abs_diff", since = "1.81.0")]
    #[rustc_const_stable(feature = "duration_abs_diff", since = "1.81.0")]
    #[rustc_allow_const_fn_unstable(const_option)]
    #[must_use = "this returns the result of the operation, \
                  without modifying the original"]
    #[inline]
    pub const fn abs_diff(self, other: Duration) -> Duration
    { /* 実装は省略 */ }
}

selfotherの絶対差を計算する。

サンプル

use std::time::Duration;

assert_eq!(Duration::new(100, 0).abs_diff(Duration::new(80, 0)), Duration::new(20, 0));
assert_eq!(Duration::new(100, 400_000_000).abs_diff(Duration::new(110, 0)), Duration::new(9, 600_000_000));

IoSlice::advance

原典

impl<'a> IoSlice<'a> {
    #[stable(feature = "io_slice_advance", since = "1.81.0")]
    #[inline]
    pub fn advance(&mut self, n: usize)
    { /* 実装は省略 */ }
}

スライスの内部カーソルを進める。

複数のバッファのカーソルを進めるためにはIoSlice::advance_slicesを参照。

パニック

スライスの末尾を超えて進めようとするとパニックする。

サンプル

use std::io::IoSlice;
use std::ops::Deref;

let data = [1; 8];
let mut buf = IoSlice::new(&data);

// 3バイトを既読にする
buf.advance(3);
assert_eq!(buf.deref(), [1; 5].as_ref());

IoSlice::advance_slices

原典

impl<'a> IoSlice<'a> {
    #[stable(feature = "io_slice_advance", since = "1.81.0")]
    #[inline]
    pub fn advance_slices(bufs: &mut &mut [IoSlice<'a>], n: usize)
    { /* 実装は省略 */ }
}

スライスのスライスを進める。

完全に進められたIoSliceを削除してスライスを縮小する。 IoSliceのカーソルが途中にある場合、そのカーソルから開始されるように変更される。

例えば8バイトのIoSlice2つのスライスがある場合に10バイト進めると、 2バイト進められた2番目のIoSliceのみが含まれる結果になる。

パニック

スライスの末尾を超えて進めようとするとパニックする。

サンプル

use std::io::IoSlice;
use std::ops::Deref;

let buf1 = [1; 8];
let buf2 = [2; 16];
let buf3 = [3; 8];
let mut bufs = &mut [
    IoSlice::new(&buf1),
    IoSlice::new(&buf2),
    IoSlice::new(&buf3),
][..];

// 10バイトを書き込み済みにする
IoSlice::advance_slices(&mut bufs, 10);
assert_eq!(bufs[0].deref(), [2; 14].as_ref());
assert_eq!(bufs[1].deref(), [3; 8].as_ref());

IoSliceMut::advance

原典

impl<'a> IoSliceMut<'a> {
    #[stable(feature = "io_slice_advance", since = "1.81.0")]
    #[inline]
    pub fn advance(&mut self, n: usize)
    { /* 実装は省略 */ }
}

スライスの内部カーソルを進める。

複数のバッファのカーソルを進めるためにはIoSliceMut::advance_slicesを参照。

パニック

スライスの末尾を超えて進めようとするとパニックする。

サンプル

use std::io::IoSliceMut;
use std::ops::Deref;

let mut data = [1; 8];
let mut buf = IoSliceMut::new(&mut data);

// 3バイトを既読にする
buf.advance(3);
assert_eq!(buf.deref(), [1; 5].as_ref());

IoSliceMut::advance_slices

原典

impl<'a> IoSliceMut<'a> {
    #[stable(feature = "io_slice_advance", since = "1.81.0")]
    #[inline]
    pub fn advance_slices(bufs: &mut &mut [IoSliceMut<'a>], n: usize)
    { /* 実装は省略 */ }
}

スライスのスライスを進める。

完全に進められたIoSliceMutを削除してスライスを縮小する。 IoSliceMutのカーソルが途中にある場合、そのカーソルから開始されるように変更される。

例えば8バイトのIoSliceMut2つのスライスがある場合に10バイト進めると、 2バイト進められた2番目のIoSliceMutのみが含まれる結果になる。

パニック

スライスの末尾を超えて進めようとするとパニックする。

サンプル

use std::io::IoSliceMut;
use std::ops::Deref;

let mut buf1 = [1; 8];
let mut buf2 = [2; 16];
let mut buf3 = [3; 8];
let mut bufs = &mut [
    IoSliceMut::new(&mut buf1),
    IoSliceMut::new(&mut buf2),
    IoSliceMut::new(&mut buf3),
][..];

// 10バイトを既読にする
IoSliceMut::advance_slices(&mut bufs, 10);
assert_eq!(bufs[0].deref(), [2; 14].as_ref());
assert_eq!(bufs[1].deref(), [3; 8].as_ref());

PanicHookInfo

原典

#[stable(feature = "panic_hook_info", since = "1.81.0")]
#[derive(Debug)]
pub struct PanicHookInfo<'a>
{ /* 定義は省略 */ }

パニックに関する情報を提供する構造体。

PanicHookInfo構造体はset_hook関数で設定されたパニックフックに渡される。

サンプル

use std::panic;

panic::set_hook(Box::new(|panic_info| {
    println!("パニック発生:{panic_info}");
}));

panic!("致命的なシステム障害");

PanicInfo::message

原典

impl<'a> PanicInfo<'a> {
    #[must_use]
    #[stable(feature = "panic_info_message", since = "1.81.0")]
    pub fn message(&self) -> PanicMessage<'_>
    { /* 実装は省略 */ }
}

panic!マクロに渡されたメッセージ。

サンプル

このメソッドの戻り値はDisplayを実装しているため、write!()などのマクロに直接渡すことができる。

#[panic_handler]
fn panic_handler(panic_info: &PanicInfo<'_>) -> ! {
    write!(DEBUG_OUTPUT, "パニック:{}", panic_info.message());
    loop {}
}

PanicMessage

原典

#[stable(feature = "panic_info_message", since = "1.81.0")]
pub struct PanicMessage<'a>
{ /* 定義は省略 */ }

panic!()マクロに渡されたメッセージ。

この型のDisplay実装はpanic!()マクロに渡された引数でメッセージを形式化する。

PanicInfo::messageメソッドも参照。

変更点リスト

言語

コンパイラ

ライブラリ

安定化されたAPI

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

Cargo

互換性メモ

  • コンパイラwasm32-wasiターゲットの使用に警告を発するようになり、代わりにwasm32-wasip1ターゲットへの切り替えを求められる。どちらのターゲットも同一でありwasm32-wasiが改名されただけである。このWASIターゲットへの変更は2025年1月にwasm32-wasiを削除するために行われている

  • std::panic::PanicInfostd::panic::PanicHookInfoに改名した。古い名前は別名として引き続き機能するものの、Rust 1.82.0からは非推奨の警告が発されるようになる。

    core::panic::PanicInfoは変わらないままであるが別の型となった。

    これは、これらの型は異なる役割を持つからである。std::panic::PanicHookInfoは(パニックが任意のデータを伴う可能性がある)std環境におけるパニック時のフックへの引数であり、一方core::panic::PanicInfoは(パニックが形式化済み文言のみを伴う)no_std環境における#[panic_handler]への引数である。これらの型を分離することにより、std::panic::PanicHookInfo::payload_as_str()core::panic::PanicInfo::message()のような、より便利なメソッドを追加できるようになる。

内部の変更

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

関連リンク

さいごに

次のリリースのRust 1.82は10/18(金)にリリースされる予定です。 Rust 1.82では非安全属性や存在型でのライフタイムの指定、また最低限の網羅的パターンが使えるようになる予定です。

ライセンス表記

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



以上の内容はhttps://aznhe21.hatenablog.com/entry/2024/09/06/rust-1.81より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14