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


Rust 1.94を早めに深掘り

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

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

ピックアップ

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

スライスを固定長の部分スライスでずらしながらループできるようになった

配列を1つずつずらしながらループしたい場合、これまでは<[T]>::windowsを使えました。 ただwindowsは要素数を実行時引数で指定することから、 要素数が固定であっても可変長スライスとしてしか扱えず不要な分岐が発生していました。

Rust 1.94からは<[T]>::array_windowsが使えるようになり、スライスのウィンドウを固定長スライスとして扱えるようになりました。

fn main() {
    let slice: &[i32] = &[0, 1, 2];

    // windowsでは&[i32]としてしか扱えないため無駄な分岐が必要
    for window in slice.windows(2) {
        let [a, b] = window else { unreachable!() };
        println!("{a}, {b}");
    }

    // array_windowsでは&[i32; 2]として扱える
    for [a, b] in slice.array_windows() {
        println!("{a}, {b}");
    }
}

なお重ならない部分スライスを扱う場合、実行時引数では<[T]>::chunksを使うので 固定長ではarray_chunksを探してしまいますが、 この機能は<[T]>::as_chunksとしてRust 1.88から実装されています。 これはchunksがイテレータを返すのに対しas_chunks&[[T; N]](を含むタプル)を返すためです。

CargoがTOML 1.1に対応した

CargoがTOML 1.1に対応しました。 主な変更点としてはインラインテーブルが複数行で書けるようになったことが挙げられます。

[dependencies]
# これまでは1行で書く必要があった
tokio = {
    version = "1.0",
    features = [
        "rt-multi-thread", # 複数スレッドランタイム
        "net",             # ネットワーク
        "time",            # 時間関連
        "macros",          # マクロ
        "sync",            # 同期プリミティブ
        "fs",              # ファイルシステム
    ],
    # ↑末尾のカンマもOK
}

Cargoの設定ファイルから別のtomlを取り込めるようになった

Cargoの設定ファイルで別のtomlファイルを取り込めるようになりました。 includeに配列で、そのファイル基準のパスを記述します。 配列の後ろの方が優先度が高く、かつそのファイル自体の記述が最も優先されます。 そのためincludeはファイルの一番最初に記述することが推奨されます。

なおCargo.tomlではなくCargo自体の設定ファイル(~/.cargo/config.tomlなど)の機能であることに注意してください。

例えばyadmやchezmoiなどのdotfile管理ツールを使っている場合、共通の設定とローカルで上書きする設定を分けて管理できるようになります。

# ~/.cargo/config.toml
include = [
    # 共通する設定。優先度低
    # ファイルが存在しない場合はエラーになる
    "config.common.toml",
    # ローカルで上書きする設定。優先度高
    # optional = trueを付けるとファイルが存在しなくてもエラーにならない
    { path = "config.local.toml", optional = true },
]

# それ以外の設定。優先度最高
# ~/.cargo/config.common.toml
# 開発用にmoldを使う設定
[target.'cfg(target_os = "linux")']
rustflags = ["-C", "link-arg=-fuse-ld=mold"]

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

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

<[T]>::array_windows

原典

impl<T> [T] {
    #[stable(feature = "array_windows", since = "1.94.0")]
    #[rustc_const_unstable(feature = "const_slice_make_iter", issue = "137737")]
    #[inline]
    #[track_caller]
    pub const fn array_windows<const N: usize>(&self) -> ArrayWindows<'_, T, N>
    { /* 実装は省略 */ }
}

スライスの先頭から開始し、N要素スライスで重なり合うウィンドウのイテレータを返す。

これはwindowsの定数ジェネリクス版である。

Nがスライスの長さより大きい場合、ウィンドウは1つも返されない。

パニック

Nが0の場合にパニックする。

この検査は実行時の値ではなく定数ジェネリクス引数に対して行われる。 すなわち単相化された関数について考えると、常にパニックするか、または一切パニックしないかのどちらかである。

サンプル

let slice = [0, 1, 2, 3];
let mut iter = slice.array_windows();
assert_eq!(iter.next().unwrap(), &[0, 1]);
assert_eq!(iter.next().unwrap(), &[1, 2]);
assert_eq!(iter.next().unwrap(), &[2, 3]);
assert!(iter.next().is_none());

<[T]>::element_offset

原典

impl<T> [T] {
    #[must_use]
    #[stable(feature = "element_offset", since = "1.94.0")]
    pub fn element_offset(&self, element: &T) -> Option<usize>
    { /* 実装は省略 */ }
}

要素への参照が指している添字を返す。

elementがスライス内の要素先頭を指していない場合はNoneを返す。

このメソッドはslice::splitのようなスライスイテレータを拡張するのに有用である。

このメソッドはポインタ演算を使うため、要素の比較は行わないことに注意。 要素を比較して添字を求めたい場合、代わりに.iter().position()を使用すること。

パニック

Tが大きさのない型場合はパニックする。

サンプル

基本的な使い方

let nums: &[u32] = &[1, 7, 1, 1];
let num = &nums[2];

assert_eq!(num, &1);
assert_eq!(nums.element_offset(num), Some(2));

アライメントされていない要素ではNoneを返す

let arr: &[[u32; 2]] = &[[0, 1], [2, 3]];
let flat_arr: &[u32] = arr.as_flattened();

let ok_elm: &[u32; 2] = flat_arr[0..2].try_into().unwrap();
let weird_elm: &[u32; 2] = flat_arr[1..3].try_into().unwrap();

assert_eq!(ok_elm, &[0, 1]);
assert_eq!(weird_elm, &[1, 2]);

assert_eq!(arr.element_offset(ok_elm), Some(0)); // 0の要素を指す
assert_eq!(arr.element_offset(weird_elm), None); // 0と1の要素間を指す

LazyCell::get

原典

impl<T, F> LazyCell<T, F> {
    #[inline]
    #[stable(feature = "lazy_get", since = "1.94.0")]
    pub fn get(this: &LazyCell<T, F>) -> Option<&T>
    { /* 実装は省略 */ }
}

初期化済みなら値への参照を返し、それ以外(未初期化または中毒状態)ならNoneを返す。

サンプル

use std::cell::LazyCell;

let lazy = LazyCell::new(|| 92);

assert_eq!(LazyCell::get(&lazy), None);
let _ = LazyCell::force(&lazy);
assert_eq!(LazyCell::get(&lazy), Some(&92));

LazyCell::get_mut

原典

impl<T, F> LazyCell<T, F> {
    #[inline]
    #[stable(feature = "lazy_get", since = "1.94.0")]
    pub fn get_mut(this: &mut LazyCell<T, F>) -> Option<&mut T>
    { /* 実装は省略 */ }
}

初期化済みなら値への可変参照を返し、それ以外(未初期化または中毒状態)ならNoneを返す。

サンプル

use std::cell::LazyCell;

let mut lazy = LazyCell::new(|| 92);

assert_eq!(LazyCell::get_mut(&mut lazy), None);
let _ = LazyCell::force(&lazy);
*LazyCell::get_mut(&mut lazy).unwrap() = 44;
assert_eq!(*lazy, 44);

LazyCell::force_mut

原典

impl<T, F: FnOnce() -> T> LazyCell<T, F> {
    #[inline]
    #[stable(feature = "lazy_get", since = "1.94.0")]
    pub fn force_mut(this: &mut LazyCell<T, F>) -> &mut T
    { /* 実装は省略 */ }
}

この遅延値の評価を強制し、結果への可変参照を返す。

パニック

初期化クロージャ(new()メソッドに渡したもの)がパニックすると、 そのパニックは呼び出し元へ伝播しセルは中毒状態になる。 その後、セルへの全アクセス(force()または逆参照経由)はパニックする。

サンプル

use std::cell::LazyCell;

let mut lazy = LazyCell::new(|| 92);

let p = LazyCell::force_mut(&mut lazy);
assert_eq!(*p, 92);
*p = 44;
assert_eq!(*lazy, 44);

LazyLock::get

原典

impl<T, F> LazyLock<T, F> {
    #[inline]
    #[stable(feature = "lazy_get", since = "1.94.0")]
    #[rustc_should_not_be_called_on_const_items]
    pub fn get(this: &LazyLock<T, F>) -> Option<&T>
    { /* 実装は省略 */ }
}

初期化済みなら値への参照を返し、それ以外(未初期化または中毒状態)ならNoneを返す。

サンプル

use std::sync::LazyLock;

let lazy = LazyLock::new(|| 92);

assert_eq!(LazyLock::get(&lazy), None);
let _ = LazyLock::force(&lazy);
assert_eq!(LazyLock::get(&lazy), Some(&92));

LazyLock::get_mut

原典

impl<T, F> LazyLock<T, F> {
    #[inline]
    #[stable(feature = "lazy_get", since = "1.94.0")]
    pub fn get_mut(this: &mut LazyLock<T, F>) -> Option<&mut T>
    { /* 実装は省略 */ }
}

初期化済みなら値への可変参照を返し、それ以外(未初期化または中毒状態)ならNoneを返す。

サンプル

use std::sync::LazyLock;

let mut lazy = LazyLock::new(|| 92);

assert_eq!(LazyLock::get_mut(&mut lazy), None);
let _ = LazyLock::force(&lazy);
*LazyLock::get_mut(&mut lazy).unwrap() = 44;
assert_eq!(*lazy, 44);

LazyLock::force_mut

原典

impl<T, F: FnOnce() -> T> LazyLock<T, F> {
    #[inline]
    #[stable(feature = "lazy_get", since = "1.94.0")]
    pub fn force_mut(this: &mut LazyLock<T, F>) -> &mut T
    { /* 実装は省略 */ }
}

この遅延値の評価を強制し、結果への可変参照を返す。

パニック

初期化クロージャ(new()メソッドに渡したもの)がパニックすると、 そのパニックは呼び出し元へ伝播しロックは中毒状態になる。 その後、ロックへの全アクセス(force()または逆参照経由)はパニックする。

サンプル

use std::sync::LazyLock;

let mut lazy = LazyLock::new(|| 92);

let p = LazyLock::force_mut(&mut lazy);
assert_eq!(*p, 92);
*p = 44;
assert_eq!(*lazy, 44);

std::iter::Peekable::next_if_map

原典

impl<I: Iterator> Peekable<I> {
    #[stable(feature = "peekable_next_if_map", since = "1.94.0")]
    pub fn next_if_map<R>(&mut self, f: impl FnOnce(I::Item) -> Result<R, I::Item>) -> Option<R>
    { /* 実装は省略 */ }
}

このイテレータの次の値を消費して関数fを適用し、クロージャがOkを返した場合はその結果を返す。

クロージャがErrを返した場合は、次の反復のために値を戻す。

Err側の中身は通常クロージャに渡した元の値であるが、それは必須ではない。 別の値を返した場合は次のpeek()またはnext()呼び出しでその新しい値が得られる。 これはpeek_mut()の出力を書き換えるのと似ている。

クロージャがパニックした場合、次の値は必ず消費されて破棄される。 クロージャが戻し用のErr値を返していないため、パニックを捕捉したとしても同様である。

next_if_map_mutも参照。

サンプル

文字イテレータの先頭にある10進数を読み取る例。
let mut iter = "125 GOTO 10".chars().peekable();
let mut line_num = 0_u32;
while let Some(digit) = iter.next_if_map(|c| c.to_digit(10).ok_or(c)) {
    line_num = line_num * 10 + digit;
}
assert_eq!(line_num, 125);
assert_eq!(iter.collect::<String>(), " GOTO 10");
独自型を照合する例。
#[derive(Debug, PartialEq, Eq)]
enum Node {
    Comment(String),
    Red(String),
    Green(String),
    Blue(String),
}

/// 連続するすべての`Comment`ノードを1つのノードに結合する
fn combine_comments(nodes: Vec<Node>) -> Vec<Node> {
    let mut result = Vec::with_capacity(nodes.len());
    let mut iter = nodes.into_iter().peekable();
    let mut comment_text = None::<String>;
    loop {
        // 通常`.next_if_map()`のクロージャは入力でマッチし、
        // 目的のパターンを`Ok`に抽出し、残りを`Err`に入れる。
        while let Some(text) = iter.next_if_map(|node| match node {
            Node::Comment(text) => Ok(text),
            other => Err(other),
        }) {
            comment_text.get_or_insert_default().push_str(&text);
        }

        if let Some(text) = comment_text.take() {
            result.push(Node::Comment(text));
        }
        if let Some(node) = iter.next() {
            result.push(node);
        } else {
            break;
        }
    }
    result
}
#[derive(Debug, PartialEq, Eq)]
enum Node {
    Comment(String),
    Red(String),
    Green(String),
    Blue(String),
}

/// 連続するすべての`Comment`ノードを1つのノードに結合する
fn combine_comments(nodes: Vec<Node>) -> Vec<Node> {
    let mut result = Vec::with_capacity(nodes.len());
    let mut iter = nodes.into_iter().peekable();
    let mut comment_text = None::<String>;
    loop {
        // 通常`.next_if_map()`のクロージャは入力でマッチし、
        // 目的のパターンを`Ok`に抽出し、残りを`Err`に入れる。
        while let Some(text) = iter.next_if_map(|node| match node {
            Node::Comment(text) => Ok(text),
            other => Err(other),
        }) {
            comment_text.get_or_insert_default().push_str(&text);
        }

        if let Some(text) = comment_text.take() {
            result.push(Node::Comment(text));
        }
        if let Some(node) = iter.next() {
            result.push(node);
        } else {
            break;
        }
    }
    result
}
assert_eq!( // 文書が煩雑にならないようテストを非表示
    combine_comments(vec![
        Node::Comment("The".to_owned()),
        Node::Comment("Quick".to_owned()),
        Node::Comment("Brown".to_owned()),
        Node::Red("Fox".to_owned()),
        Node::Green("Jumped".to_owned()),
        Node::Comment("Over".to_owned()),
        Node::Blue("The".to_owned()),
        Node::Comment("Lazy".to_owned()),
        Node::Comment("Dog".to_owned()),
    ]),
    vec![
        Node::Comment("TheQuickBrown".to_owned()),
        Node::Red("Fox".to_owned()),
        Node::Green("Jumped".to_owned()),
        Node::Comment("Over".to_owned()),
        Node::Blue("The".to_owned()),
        Node::Comment("LazyDog".to_owned()),
    ],
)

std::iter::Peekable::next_if_map_mut

原典

impl<I: Iterator> Peekable<I> {
    #[stable(feature = "peekable_next_if_map", since = "1.94.0")]
    pub fn next_if_map_mut<R>(&mut self, f: impl FnOnce(&mut I::Item) -> Option<R>) -> Option<R>
    { /* 実装は省略 */ }
}

イテレータの次の値への可変参照を渡して関数fを適用し、 fSomeを返した場合はその結果を返してイテレータを前進させる。

fNoneを返した場合は、次の値は次回反復のために保持される。

fがパニックした場合は、fSomeを返したときと同様にその要素は消費される。 その値は破棄される。

これはnext_if_mapと似ているが、要素の所有権をfへ渡さない点が異なる。 fがどのみち要素を複製する場合には、こちらが望ましいことがある。

サンプル

文字イテレータの先頭にある10進数を読み取る例。

let mut iter = "125 GOTO 10".chars().peekable();
let mut line_num = 0_u32;
while let Some(digit) = iter.next_if_map_mut(|c| c.to_digit(10)) {
    line_num = line_num * 10 + digit;
}
assert_eq!(line_num, 125);
assert_eq!(iter.collect::<String>(), " GOTO 10");

f32::consts::EULER_GAMMA

原典

#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_diagnostic_item = "f32_consts_mod"]
pub mod consts {
    #[stable(feature = "euler_gamma_golden_ratio", since = "1.94.0")]
    pub const EULER_GAMMA: f32 = 0.577215664901532860606512090082402431_f32;
}

オイラー・マスケローニ定数(γ)

f64::consts::EULER_GAMMA

原典

#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_diagnostic_item = "f64_consts_mod"]
pub mod consts {
    #[stable(feature = "euler_gamma_golden_ratio", since = "1.94.0")]
    pub const EULER_GAMMA: f64 = 0.577215664901532860606512090082402431_f64;
}

オイラー・マスケローニ定数(γ)

f32::consts::GOLDEN_RATIO

原典

#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_diagnostic_item = "f32_consts_mod"]
pub mod consts {
    #[stable(feature = "euler_gamma_golden_ratio", since = "1.94.0")]
    pub const GOLDEN_RATIO: f32 = 1.618033988749894848204586834365638118_f32;
}

黄金比(φ)

f64::consts::GOLDEN_RATIO

原典

#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_diagnostic_item = "f64_consts_mod"]
pub mod consts {
    #[stable(feature = "euler_gamma_golden_ratio", since = "1.94.0")]
    pub const GOLDEN_RATIO: f64 = 1.618033988749894848204586834365638118_f64;
}

黄金比(φ)

変更点リスト

言語

プラットフォーム対応

Rustのティア付けされたプラットフォーム対応の詳細はPlatform Supportのページ(※訳注:英語)を参照

ライブラリ

安定化されたAPI

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

Cargo

  • include設定キーを安定化。最上位のinclude設定キーで追加の設定ファイルを読み込めるため、プロジェクトや環境をまたいだCargo設定の整理・共有・管理がしやすくなる。文書 #16284
  • レジストリ添字のpubtimeフィールドを安定化。クレートのバージョンが公開された時刻を記録することで、将来的には時刻による依存解決できるようになる。なおcrates.ioでは新しいバージョンが公開された際に既存パッケージへ段階的に値を補完するため、まだpubtimeを持たないクレートもある。#16369 #16372
  • Cargoがマニフェストと設定ファイルでTOML v1.1を解釈するようになった。これらの機能をCargo.tomlで使うと開発時MSRVは上がるが、公開されるマニフェストは旧来のパーサとも互換性を保つ。#16415
  • CARGO_BIN_EXE_<crate>を実行時に利用可能にした

互換性メモ

内部の変更

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

関連リンク

さいごに

次のリリースのRust 1.95は4/16(金)にリリースされる予定です。 Rust 1.95ではパターンマッチでif-letが使えるようになったり、 パターンマッチで表明ができる(debug_)assert_matches!が使えるようになったり、 #[cfg]の値でパターンマッチっぽく分岐できるcfg_select!が使えるようになったりする予定です。

ライセンス表記

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



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

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