以下の内容はhttps://aznhe21.hatenablog.com/entry/2025/12/12/rust-1.92より取得しました。


Rust 1.92を早めに深掘り

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

この記事は原文の理解や和訳のために一部生成AIを使用していますが、すべて筆者の考えに基づく文章で構成しており、 漫然と生成AIを使用しているものではありません。

ピックアップ

個人的に注目する変更点、及び公式ブログで紹介されている内容を「ピックアップ」としてまとめました。 全ての変更点を網羅したリストは変更点リストをご覧ください。

RwLockの書き込みロックを読み込みロックに降格できるようになった

RwLock::write()を呼び出した結果の[RwLockWriteGuard]は、長期間保持していると他スレッドでのRwLock::read()をブロックしてしまいます。 そのため基本的には最低限の処理期間だけ保持しておきすぐにdropすべきです。

ただ、書き込んだあとに読み込む必要がある場合は別途RwLock::read()を呼び出す必要があるため、面倒で非効率でした。

Rust 1.92で[RwLockWriteGuard::downgrade]が使えるようになり、 ロックを取得したままRwLockReadGuardに降格させることができるようになりました。 もちろんこれを呼び出すとRwLock::read()の呼び出しはすぐに制御を返すようになります。

この機能のためにparking_lotクレートを使用していた場合は標準ライブラリだけで完結できるようになり便利ですね。

ヒープへゼロ初期化できるようになった

C言語callocのように、メモリをヒープに確保しつつゼロで初期化することができるようになりました。

これまではBox::uninitなどで確保したあとに明示的にゼロを書き込む必要がありました。 Rust 1.92からはBoxRcArcnew_zeroed及びnew_zeroed_sliceで直接ゼロ初期化することができます。 プラットフォームによっては自分でゼロを書き込むよりもコストが安いこともあるため、 パフォーマンスを気にするプログラムでは使うことを検討しても良いでしょう。

なお戻り値はBox<MaybeUninit<T>>の形であり、Box<T>の形にするにはunsafeであるassume_initの呼び出しが必要です。

never型の拒否リント

never型(!型)の安定化に向け作業が続いています。

Rust 1.92では前方互換性のためのリントnever_type_fallback_flowing_into_unsafedependency_on_unit_never_type_fallbackが 既定で拒否されるようになり、それらが検出された場合はエラーとなります。

これらは依然としてリントであるため#[allow]することもできますし、依存クレートのエラーは報告されません。

これらのリントが検出したコードはnever型が安定化した場合は壊れることになるため、出来るだけ修正するようにしてください。

このリントの影響を受けるクレートは500個に及ぶと想定されます。 とは言えこれは破壊的変更ではなく将来的にnever型を安定化するためのものであるため許容範囲と考えられます。

unused_must_useResult<(), UninhabitedType>で警告されなくなった

unused_must_useは関数やその戻り型に#[must_use]を付けることで、関数の戻り値を無視しないよう警告するためのものです。 例えば戻り型がResultの場合は?.expect("...")などを使って戻り値を無視しないよう教えてくれます。

しかしResultを返す一部関数において、そのエラー型が実際には「非居住(uninhabited)」、 つまりその型のいかなる値も構築出来ない(!Infallibleなど)場合があります。

Rust 1.92からはリントunused_must_useResult<(), UninhabitedType>ControlFlow<UninhabitedType, ()>では警告を出さなくなりました。 具体的にはResult<(), Infallible>は警告されません。これによりエラーが発生しない場合のチェックを回避できます。

use core::convert::Infallible;
fn can_never_fail() -> Result<(), Infallible> {
    // ...
    Ok(())
}

fn main() {
    can_never_fail();
}

主にエラーを関連型に持つトレイトのパターンではInfallibleをエラー型とする場合に有用です。

trait UsesAssocErrorType {
    type Error;
    fn method(&self) -> Result<(), Self::Error>;
}

struct CannotFail;
impl UsesAssocErrorType for CannotFail {
    type Error = core::convert::Infallible;
    fn method(&self) -> Result<(), Self::Error> {
        Ok(())
    }
}

struct CanFail;
impl UsesAssocErrorType for CanFail {
    type Error = std::io::Error;
    fn method(&self) -> Result<(), Self::Error> {
        Err(std::io::Error::other("何か変"))
    }
}

fn main() {
    CannotFail.method(); // 警告なし
    CanFail.method(); // 警告:unused `Result` that must be used
}

Linux-Cpanic=abortが有効な際に巻き戻しテーブルを出力

Rust 1.22以前は-Cpanic=abort時でもバックトレースが動作していましたが、 Rust 1.23以降は壊れており巻き戻しテーブルが出力されていませんでした。 この回避策としてRust 1.45では-Cforce-unwind-tables=yesが導入されました。

Rust 1.92では-Cpanic=abort指定時でも既定で巻き戻しテーブルを出力するようになり、 バックトレースが正しく動作するようになりました。 巻き戻しテーブルが不要な場合は-Cforce-unwind-tables=noにより明示的に出力を無効化してください。

#[macro_export]の入力を検証

最近のリリースではコンパイラでの組み込み属性の処理方法に関して多くの変更が入っています。 組み込み属性に関するエラーメッセージと警告が大幅に改善され、100以上の組み込み属性において診断結果の一貫性が向上するはずです。

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

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

NonZero<u{N}>::div_ceil

原典

impl NonZero<u8> {
    #[stable(feature = "unsigned_nonzero_div_ceil", since = "1.92.0")]
    #[rustc_const_stable(
        feature = "unsigned_nonzero_div_ceil",
        since = "1.92.0"
    )]
    #[must_use = "this returns the result of the operation, \
                  without modifying the original"]
    #[inline]
    pub const fn div_ceil(self, rhs: Self) -> Self
    { /* 実装は省略 */ }
}

selfrhsで割った値を計算し、結果を正の無限大方向に切り上げる。

結果は必ずゼロ以外になる。

サンプル

let one = NonZero::new(1u8).unwrap();
let max = NonZero::new(u8::MAX).unwrap();
assert_eq!(one.div_ceil(max), one);

let two = NonZero::new(2u8).unwrap();
let three = NonZero::new(3u8).unwrap();
assert_eq!(three.div_ceil(two), two);
use std::num::NonZero;
let one = NonZero::new(1u8).unwrap();
let max = NonZero::new(u8::MAX).unwrap();
assert_eq!(one.div_ceil(max), one);

let two = NonZero::new(2u8).unwrap();
let three = NonZero::new(3u8).unwrap();
assert_eq!(three.div_ceil(two), two);

Location::file_as_c_str

原典

impl<'a> Location<'a> {
    #[must_use]
    #[inline]
    #[stable(feature = "file_with_nul", since = "1.92.0")]
    #[rustc_const_stable(feature = "file_with_nul", since = "1.92.0")]
    pub const fn file_as_c_str(&self) -> &'a CStr
    { /* 実装は省略 */ }
}

元ファイルの名前をヌル終端のCStrとして返す。

CやC++__FILE__std::source_location::file_nameなど、 ヌル終端のconst char*を期待するAPIとの連携に役立つ。

RwLockWriteGuard::downgrade

原典

impl<'rwlock, T: ?Sized> RwLockWriteGuard<'rwlock, T> {
    #[stable(feature = "rwlock_downgrade", since = "1.92.0")]
    pub fn downgrade(s: Self) -> RwLockReadGuard<'rwlock, T>
    { /* 実装は省略 */ }
}

書き込みロックされたRwLockWriteGuardから、読み込みロックされたRwLockReadGuardに格下げする。

RwLockWriteGuardがある時点でRwLockは既に書き込み用にロックされているため、この操作は失敗しない。

格下げ後、他の場所でもデータの読み取りが出来るようになる。

サンプル

downgradeRwLockWriteGuardの所有権を受け取り、RwLockReadGuardを返す。

use std::sync::{RwLock, RwLockWriteGuard};

let rw = RwLock::new(0);

let mut write_guard = rw.write().unwrap();
*write_guard = 42;

let read_guard = RwLockWriteGuard::downgrade(write_guard);
assert_eq!(42, *read_guard);

downgradeRwLockの状態を排他的モードから共有モードへと原子的に変更する。このため、格下げを行った直後に他の書き込みスレッドが割り込んでデータを書き換えることはできない。

use std::sync::{Arc, RwLock, RwLockWriteGuard};

let rw = Arc::new(RwLock::new(1));

// ロックを書き込みモードにする
let mut main_write_guard = rw.write().unwrap();

let rw_clone = rw.clone();
let evil_handle = std::thread::spawn(move || {
    // メインスレッドが`main_read_guard`をドロップするまで戻らない
    let mut evil_guard = rw_clone.write().unwrap();

    assert_eq!(*evil_guard, 2);
    *evil_guard = 3;
});

*main_write_guard = 2;

// 原子的に書き込みガードを読み込みガードに格下げする
let main_read_guard = RwLockWriteGuard::downgrade(main_write_guard);

// `downgrade`は原子的であるため、書き込みスレッドが保護されたデータを変更していることはありえない
assert_eq!(*main_read_guard, 2, "`downgrade` was not atomic");
use std::sync::{Arc, RwLock, RwLockWriteGuard};

let rw = Arc::new(RwLock::new(1));

// ロックを書き込みモードにする
let mut main_write_guard = rw.write().unwrap();

let rw_clone = rw.clone();
let evil_handle = std::thread::spawn(move || {
    // メインスレッドが`main_read_guard`をドロップするまで戻らない
    let mut evil_guard = rw_clone.write().unwrap();

    assert_eq!(*evil_guard, 2);
    *evil_guard = 3;
});

*main_write_guard = 2;

// 原子的に書き込みガードを読み込みガードに格下げする
let main_read_guard = RwLockWriteGuard::downgrade(main_write_guard);

// `downgrade`は原子的であるため、書き込みスレッドが保護されたデータを変更していることはありえない
assert_eq!(*main_read_guard, 2, "`downgrade` was not atomic");

drop(main_read_guard);
evil_handle.join().unwrap();

let final_check = rw.read().unwrap();
assert_eq!(*final_check, 3);

Box::new_zeroed

原典

impl<T> Box<T> {
    #[cfg(not(no_global_oom_handling))]
    #[inline]
    #[stable(feature = "new_zeroed_alloc", since = "1.92.0")]
    #[must_use]
    pub fn new_zeroed() -> Box<mem::MaybeUninit<T>>
    { /* 実装は省略 */ }
}

中身の初期化されていない新しいBox型を生成する。メモリは0で埋められる。

このメソッドの正しい使い方と誤った使い方についてはMaybeUninit::zeroedの例を参照。

サンプル

let zero = Box::<u32>::new_zeroed();
let zero = unsafe { zero.assume_init() };

assert_eq!(*zero, 0)

Box::new_zeroed_slice

原典

impl<T> Box<[T]> {
    #[cfg(not(no_global_oom_handling))]
    #[stable(feature = "new_zeroed_alloc", since = "1.92.0")]
    #[must_use]
    pub fn new_zeroed_slice(len: usize) -> Box<[mem::MaybeUninit<T>]>
    { /* 実装は省略 */ }
}

中身の初期化されていない新しいBox型のスライスを生成する。メモリは0で埋められる。

このメソッドの正しい使い方と誤った使い方についてはMaybeUninit::zeroedの例を参照。

サンプル

let values = Box::<[u32]>::new_zeroed_slice(3);
let values = unsafe { values.assume_init() };

assert_eq!(*values, [0, 0, 0])

Rc::new_zeroed

原典

impl<T> Rc<T> {
    #[cfg(not(no_global_oom_handling))]
    #[stable(feature = "new_zeroed_alloc", since = "1.92.0")]
    #[must_use]
    pub fn new_zeroed() -> Rc<mem::MaybeUninit<T>>
    { /* 実装は省略 */ }
}

中身の初期化されていない新しいRc型を生成する。メモリは0で埋められる。

このメソッドの正しい使い方と誤った使い方についてはMaybeUninit::zeroedの例を参照。

サンプル

use std::rc::Rc;

let zero = Rc::<u32>::new_zeroed();
let zero = unsafe { zero.assume_init() };

assert_eq!(*zero, 0)

Rc::new_zeroed_slice

原典

impl<T> Rc<[T]> {
    #[cfg(not(no_global_oom_handling))]
    #[stable(feature = "new_zeroed_alloc", since = "1.92.0")]
    #[must_use]
    pub fn new_zeroed_slice(len: usize) -> Rc<[mem::MaybeUninit<T>]>
    { /* 実装は省略 */ }
}

中身の初期化されていない新しい参照カウント型スライスを生成する。メモリは0で埋められる。

このメソッドの正しい使い方と誤った使い方についてはMaybeUninit::zeroedの例を参照。

サンプル

use std::rc::Rc;

let values = Rc::<[u32]>::new_zeroed_slice(3);
let values = unsafe { values.assume_init() };

assert_eq!(*values, [0, 0, 0])

Arc::new_zeroed

原典

impl<T> Arc<T> {
    #[cfg(not(no_global_oom_handling))]
    #[inline]
    #[stable(feature = "new_zeroed_alloc", since = "1.92.0")]
    #[must_use]
    pub fn new_zeroed() -> Arc<mem::MaybeUninit<T>>
    { /* 実装は省略 */ }
}

中身の初期化されていない新しいArc型を生成する。メモリは0で埋められる。

このメソッドの正しい使い方と誤った使い方についてはMaybeUninit::zeroedの例を参照。

サンプル

use std::sync::Arc;

let zero = Arc::<u32>::new_zeroed();
let zero = unsafe { zero.assume_init() };

assert_eq!(*zero, 0)

Arc::new_zeroed_slice

原典

impl<T> Arc<[T]> {
    #[cfg(not(no_global_oom_handling))]
    #[inline]
    #[stable(feature = "new_zeroed_alloc", since = "1.92.0")]
    #[must_use]
    pub fn new_zeroed_slice(len: usize) -> Arc<[mem::MaybeUninit<T>]>
    { /* 実装は省略 */ }
}

中身の初期化されていない新しい原子的参照カウント型スライスを生成する。メモリは0で埋められる。

このメソッドの正しい使い方と誤った使い方についてはMaybeUninit::zeroedの例を参照。

サンプル

use std::sync::Arc;

let values = Arc::<[u32]>::new_zeroed_slice(3);
let values = unsafe { values.assume_init() };

assert_eq!(*values, [0, 0, 0])

btree_map::Entry::insert_entry

原典

impl<'a, K: Ord, V, A: Allocator + Clone> Entry<'a, K, V, A> {
    #[inline]
    #[stable(feature = "btree_entry_insert", since = "1.92.0")]
    pub fn insert_entry(self, value: V) -> OccupiedEntry<'a, K, V, A>
    { /* 実装は省略 */ }
}

エントリーの値を設定してOccupiedEntryを返す。

サンプル

use std::collections::BTreeMap;

let mut map: BTreeMap<&str, String> = BTreeMap::new();
let entry = map.entry("遊園地").insert_entry("わーい".to_string());

assert_eq!(entry.key(), &"遊園地");

btree_map::VacantEntry::insert_entry

原典

impl<'a, K: Ord, V, A: Allocator + Clone> VacantEntry<'a, K, V, A> {
    #[stable(feature = "btree_entry_insert", since = "1.92.0")]
    pub fn insert_entry(mut self, value: V) -> OccupiedEntry<'a, K, V, A>
    { /* 実装は省略 */ }
}

VacantEntryのキーに値を設定してOccupiedEntryを返す。

サンプル

use std::collections::BTreeMap;
use std::collections::btree_map::Entry;

let mut map: BTreeMap<&str, u32> = BTreeMap::new();

if let Entry::Vacant(o) = map.entry("遊園地") {
    let entry = o.insert_entry(37);
    assert_eq!(entry.get(), &37);
}
assert_eq!(map["遊園地"], 37);

変更点リスト

言語

コンパイラ

ライブラリ

安定化されたAPI

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

Cargo

Rustdoc

互換性メモ

関連リンク

さいごに

次のリリースのRust 1.93は1/23(金)にリリースされる予定です。 Rust 1.93ではスライスを配列化するメソッドが使えるようになったり、 VecDequeの先頭または末尾から条件に一致する場合だけ値を取り除くメソッドが使えるようになったりするようです。

ライセンス表記

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



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

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