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


Rust 1.88を早めに深掘り

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

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

ピックアップ

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

ifの条件式でletを繋げられるようになった

if式でletを使う場合はそれ以外の条件を書けず、無駄にif入れ子にさせられて残念な思いをした方も多いのではないのでしょうか。

Rust 1.88ではif内にletや条件式を複数繋げることができるようになり、書きやすさが格段に上がりました。

ただしこれを使うにはRust 2024エディションが必要です。 というか2024版のライフタイム周りの仕様変更はこの機能のためでした。

use std::io::Read;

fn main() {
    let x = Some("hoge".to_string());

    // Option::is_some_andを使うにはas_refが邪魔だった
    if x.as_ref().is_some_and(|x| x.len() % 2 == 0) {
        println!("xの長さは偶数");
    }
    // 中身を使うにはif-letを使うが二重ifになるのがモヤる
    if let Some(x) = &x {
        if x.len() % 2 == 0 {
            println!("xの長さは偶数:{x}");
        }
    }
    // Rust 1.88(2024版)からはこんなにスッキリ
    if let Some(x) = &x && x.len() % 2 == 0 {
        println!("xの長さは偶数:{x}");
    }

    // 何重に重ねても良いし、常にtrueとなる式を書いても良い
    // が読みにくいので普通に書いた方が良い
    // (この例なら普通にstd::fs::readを使おうね)
    if let Ok(mut f) = std::fs::File::open("fuga")
        && let Ok(metadata) = f.metadata()
        && let mut x = String::with_capacity(metadata.len() as usize)
        && let Ok(_) = f.read_to_string(&mut x)
        && let Ok(x) = x.trim().parse::<usize>()
        && x % 2 == 0
    {
        println!("ファイルの中身は偶数:{x}");
    }
}

ちなみにこの機能はRust 1.64(2022年10月22日リリース)で 使えるようになる寸前のところまで行ったのですが、この 仕様変更なしには導入できないことが判明し差し戻されたという経緯がありました。 それから2年半越しにようやく正式導入されることとなり、待ち焦がれていたという人も多いことでしょう。

スライスを配列へ分割できるようになった

<[T]>::as_chunks及びその亜種が追加され、スライスからN要素の配列へのスライス(と余り)へ分割できるようになりました。

これまでも<[T]>::chunksで似たようなことは出来ましたが、要素数が固定であってもスライスでしか得られず、 無駄にtry_into()する必要があったりと美しくはありませんでした。

fn chunks(slice: &[u8]) {
    for chunk in slice.chunks(2) {
        // 型注釈が必要だったり余りを処理する必要があったりとノイズが多い
        let Ok(&[hi, lo]): Result<&[_; 2], _> = chunk.try_into() else {
            break;
        };

        let n = u16::from_ne_bytes([hi, lo]);
        println!("{n}");
    }
}

fn as_chunks(slice: &[u8]) {
    // 素直に書けてスッキリ
    let (chunks, _remainder) = slice.as_chunks::<2>();
    // 余りがないことを検査したい場合はlet-else
    // let (chunks, []) = slice.as_chunks::<2>() else { todo!() };
    for &[hi, lo] in chunks {
        let n = u16::from_ne_bytes([hi, lo]);
        println!("{n}");
    }
}

as_chunksは余りを末尾で求める不変借用版ですが、次の亜種もあります。

  • as_chunks_mut:余りを末尾で求める可変借用版
  • as_chunks_unchecked:余りがない前提の不変借用版
  • as_chunks_unchecked_mut:余りがない前提の可変借用版
  • as_rchunks:余りを先頭で求める不変借用版
  • as_rchunks_mut:余りを先頭で求める可変借用版

unchecked版は余りがない前提なのでas_rchunks_uncheckedはない)

コンパイルを常に無効化(有効化)できるようになった

#[cfg(true)]#[cfg(false)]のようにcfgの中に真偽値を入れられるようになり、 式やブロックを常に有効化・無効化できるようになりました。基本的には#[cfg(false)]の方を使うような気がします。

デバッグ時には特定のブロックをコンパイルさせたくないということはままありますが、 コメントでは範囲すべてを編集する必要があるなど少し面倒でした。

これまでも未定義の条件を使い#[cfg(FALSE)]のように書くこともできましたが、 これではunexpected `cfg` condition name: `FALSE`という警告が出るため少し不便でした。

fn super_convenient_func() {
    // 依存から一旦tokioを外したいので無効化しとく
    #[cfg(false)]
    {
        tokio::spawn(async {
            println!("mettya benri!");
        });
    }
}

cargo実行時に自動でゴミが掃除されるようになった

~/.cargo以下には全プロジェクトの依存クレートがダウンロード・キャッシュされますが、 長い間開発していると古いクレートがどんどん貯まっていきます。 このキャッシュは使われていないものだけでも数GBになることもあり、邪魔と思ったことがあるかもしれません。

Rust 1.88以降のCargoでは、(一部cargoコマンド実行時に)ダウンロードから3ヶ月経過後に自動で掃除してくれるようになります。 既定では1日に1回発動しますが、この頻度は設定値gc.auto.frequencyで変更することもできます。

なお、これを手動で実行できるコマンドは設計が未確定のためまだ使えません

またtargetディレクトリのお掃除機能は別途検討中の様です。

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

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

Cell::update

原典

impl<T: Copy> Cell<T> {
    #[inline]
    #[stable(feature = "cell_update", since = "1.88.0")]
    pub fn update(&self, f: impl FnOnce(T) -> T)
    { /* 実装は省略 */ }
}

格納されている値を関数により更新する。

サンプル

use std::cell::Cell;

let c = Cell::new(5);
c.update(|x| x + 1);
assert_eq!(c.get(), 6);

HashMap::extract_if

原典

impl<K, V, S> HashMap<K, V, S> {
    #[inline]
    #[rustc_lint_query_instability]
    #[stable(feature = "hash_extract_if", since = "1.88.0")]
    pub fn extract_if<F>(&mut self, pred: F) -> ExtractIf<'_, K, V, F>
    where
        F: FnMut(&K, &mut V) -> bool,
    { /* 実装は省略 */ }
}

要素(キーと値の組)を削除すべきかについて、クロージャを使って判断するイテレータを生成する。

クロージャtrueを返すと要素は連想配列から削除され、イテレータから返される。 クロージャfalseを返すかパニックした場合、その要素は連想配列に残ってイテレータからは返されない。

このイテレータでは各要素を削除するかどうかにかかわらず、クロージャ内で値を変更できる。

そのままドロップしたり途中で繰り返しをやめたりした時など、戻り値のExtractIfを処理しきらなかった場合 残った要素は保持される。

戻り値のイテレータが不要な場合は述語関数を反転させてretainを使うと良い。

サンプル

元の連想配列を再利用し、キーを偶数と奇数に分割する。

use std::collections::HashMap;

let mut map: HashMap<i32, i32> = (0..8).map(|x| (x, x)).collect();
let extracted: HashMap<i32, i32> = map.extract_if(|k, _v| k % 2 == 0).collect();

let mut evens = extracted.keys().copied().collect::<Vec<_>>();
let mut odds = map.keys().copied().collect::<Vec<_>>();
evens.sort();
odds.sort();

assert_eq!(evens, vec![0, 2, 4, 6]);
assert_eq!(odds, vec![1, 3, 5, 7]);

HashSet::extract_if

原典

impl<T, S> HashSet<T, S> {
    #[inline]
    #[rustc_lint_query_instability]
    #[stable(feature = "hash_extract_if", since = "1.88.0")]
    pub fn extract_if<F>(&mut self, pred: F) -> ExtractIf<'_, T, F>
    where
        F: FnMut(&T) -> bool,
    { /* 実装は省略 */ }
}

要素を削除すべきかについて、クロージャを使って判断するイテレータを生成する。

クロージャtrueを返すと要素は集合から削除され、イテレータから返される。 クロージャfalseを返すかパニックした場合、その要素は集合に残ってイテレータからは返されない。

そのままドロップしたり途中で繰り返しをやめたりした時など、戻り値のExtractIfを処理しきらなかった場合 残った要素は保持される。

戻り値のイテレータが不要な場合は述語関数を反転させてretainを使うと良い。

サンプル

元の集合を再利用し、偶数と奇数に分割する。

use std::collections::HashSet;

let mut set: HashSet<i32> = (0..8).collect();
let extracted: HashSet<i32> = set.extract_if(|v| v % 2 == 0).collect();

let mut evens = extracted.into_iter().collect::<Vec<_>>();
let mut odds = set.into_iter().collect::<Vec<_>>();
evens.sort();
odds.sort();

assert_eq!(evens, vec![0, 2, 4, 6]);
assert_eq!(odds, vec![1, 3, 5, 7]);

hint::select_unpredictable

原典

#[inline(always)]
#[stable(feature = "select_unpredictable", since = "1.88.0")]
pub fn select_unpredictable<T>(condition: bool, true_val: T, false_val: T) -> T
{ /* 実装は省略 */ }

conditionの値に応じてtrue_valまたはfalse_valを返すが、 conditionがCPUの分岐予測器によって正しく予測されにくいことをコンパイラに伝える。

この関数は、機能的には次のコードと同じである。

fn select_unpredictable<T>(b: bool, true_val: T, false_val: T) -> T {
    if b { true_val } else { false_val }
}

ただし、生成される機械語が異なる場合がある。特に、条件付き移動命令や選択命令(x86cmovやARMのcselなど)がある プラットフォームでは最適化によって分岐を避ける命令が使われることがあり、 (二分探索の実装など)分岐予測が難しい場合に性能向上が期待できる。

ただし、このような引き下げ(lowering)が必ず行われるとは限らず(どのプラットフォームでも保証されない)、 暗号用途などで定数時間動作を書きたい場合には頼るべきではない。 また、conditionが予測しやすい場合にはこの引き下げ(lowering)により逆に性能が悪化することもあるため、 この関数が有用かどうかは実際に計測して判断することを勧める。

サンプル

値を2つの入れ物へ均等に振り分ける

use std::hash::BuildHasher;
use std::hint;

fn append<H: BuildHasher>(hasher: &H, v: i32, bucket_one: &mut Vec<i32>, bucket_two: &mut Vec<i32>) {
    let hash = hasher.hash_one(&v);
    let bucket = hint::select_unpredictable(hash % 2 == 0, bucket_one, bucket_two);
    bucket.push(v);
}
use std::hash::BuildHasher;
use std::hint;

fn append<H: BuildHasher>(hasher: &H, v: i32, bucket_one: &mut Vec<i32>, bucket_two: &mut Vec<i32>) {
    let hash = hasher.hash_one(&v);
    let bucket = hint::select_unpredictable(hash % 2 == 0, bucket_one, bucket_two);
    bucket.push(v);
}
let hasher = std::collections::hash_map::RandomState::new();
let mut bucket_one = Vec::new();
let mut bucket_two = Vec::new();
append(&hasher, 42, &mut bucket_one, &mut bucket_two);
assert_eq!(bucket_one.len() + bucket_two.len(), 1);

proc_macro::Span::line

原典

impl Span {
    #[stable(feature = "proc_macro_span_location", since = "1.88.0")]
    pub fn line(&self) -> usize
    { /* 実装は省略 */ }
}

Spanが開始する位置の、ソースファイルにおける1始まりの行番号。

Spanが終了する位置の行番号を取得したい場合はspan.end().line()を使うこと。

proc_macro::Span::column

原典

impl Span {
    #[stable(feature = "proc_macro_span_location", since = "1.88.0")]
    pub fn column(&self) -> usize
    { /* 実装は省略 */ }
}

Spanが開始する位置の、ソースファイルにおける1始まりの列番号。

Spanが終了する位置の列番号を取得したい場合はspan.end().column()を使うこと。

proc_macro::Span::start

原典

impl Span {
    #[stable(feature = "proc_macro_span_location", since = "1.88.0")]
    pub fn start(&self) -> Span
    { /* 実装は省略 */ }
}

このSpanの直前を指す空のSpanを生成する。

proc_macro::Span::end

原典

impl Span {
    #[stable(feature = "proc_macro_span_location", since = "1.88.0")]
    pub fn end(&self) -> Span
    { /* 実装は省略 */ }
}

このSpanの直後を指す空のSpanを作成する。

proc_macro::Span::file

原典

impl Span {
    #[stable(feature = "proc_macro_span_file", since = "1.88.0")]
    pub fn file(&self) -> String
    { /* 実装は省略 */ }
}

表示用の、このSpanが発生したソースファイルのパス。

これは有効なファイルシステム上のパスとは限らない。 このパスは再マッピングされていたり(例: "/src/lib.rs")、人工的なパスだったり(例: "<command line>")する可能性がある。

proc_macro::Span::local_file

原典

impl Span {
    #[stable(feature = "proc_macro_span_file", since = "1.88.0")]
    pub fn local_file(&self) -> Option<PathBuf>
    { /* 実装は省略 */ }
}

ローカルファイルシステム上における、このSpanが発生したソースファイルのパス。

これはディスク上の実際のパスであり、パスの再マッピングの影響を受けない。

このパスはマクロの出力に埋め込むべきではない。代わりに file() を使用すること。

<[T]>::as_chunks

原典

impl<T> [T] {
    #[stable(feature = "slice_as_chunks", since = "1.88.0")]
    #[rustc_const_stable(feature = "slice_as_chunks", since = "1.88.0")]
    #[inline]
    #[track_caller]
    #[must_use]
    pub const fn as_chunks<const N: usize>(&self) -> (&[[T; N]], &[T])
    { /* 実装は省略 */ }
}

スライスをN要素配列のスライスと、長さがN未満の余りスライスに分割する。

余りは割り算の余りとしての意味がある。let (chunks, remainder) = slice.as_chunks()とした場合、次が成り立つ。

  • chunks.len()slice.len() / Nに等しい
  • remainder.len()slice.len() % Nに等しい
  • slice.len()chunks.len() * N + remainder.len()に等しい

配列塊はas_flattenedにより再びTのスライスに戻すことができる。

パニック

Nがゼロの場合はパニックする。

この検査は、実行時値ではなく定数の総称引数に対して行われるため、 特定の単一化(monomorphization)では常にパニックし、それ以外では決してパニックしない。

サンプル

let slice = ['l', 'o', 'r', 'e', 'm'];
let (chunks, remainder) = slice.as_chunks();
assert_eq!(chunks, &[['l', 'o'], ['r', 'e']]);
assert_eq!(remainder, &['m']);

スライスがちょうど割り切れることを期待する場合は、let-else構文と空スライスパターンを組み合わせると良い。

let slice = ['R', 'u', 's', 't'];
let (chunks, []) = slice.as_chunks::<2>() else {
    panic!("sliceの長さが偶数ではない didn't have even length")
};
assert_eq!(chunks, &[['R', 'u'], ['s', 't']]);

<[T]>::as_chunks_mut

原典

impl<T> [T] {
    #[stable(feature = "slice_as_chunks", since = "1.88.0")]
    #[rustc_const_stable(feature = "slice_as_chunks", since = "1.88.0")]
    #[inline]
    #[track_caller]
    #[must_use]
    pub const fn as_chunks_mut<const N: usize>(&mut self) -> (&mut [[T; N]], &mut [T])
    { /* 実装は省略 */ }
}

スライスをN要素配列のスライスと、長さがN未満の余りスライスに分割する。

余りは割り算の余りとしての意味がある。let (chunks, remainder) = slice.as_chunks_mut()とした場合、次が成り立つ。

  • chunks.len()slice.len() / Nに等しい
  • remainder.len()slice.len() % Nに等しい
  • slice.len()chunks.len() * N + remainder.len()に等しい

配列塊はas_flattened_mutにより再びTのスライスに戻すことができる。

パニック

この検査は、実行時値ではなく定数の総称引数に対して行われるため、 特定の単一化(monomorphization)では常にパニックし、それ以外では決してパニックしない。

サンプル

let v = &mut [0, 0, 0, 0, 0];
let mut count = 1;

let (chunks, remainder) = v.as_chunks_mut();
remainder[0] = 9;
for chunk in chunks {
    *chunk = [count; 2];
    count += 1;
}
assert_eq!(v, &[1, 1, 2, 2, 9]);

<[T]>::as_chunks_unchecked

原典

impl<T> [T] {
    #[stable(feature = "slice_as_chunks", since = "1.88.0")]
    #[rustc_const_stable(feature = "slice_as_chunks", since = "1.88.0")]
    #[inline]
    #[must_use]
    #[track_caller]
    pub const unsafe fn as_chunks_unchecked<const N: usize>(&self) -> &[[T; N]]
    { /* 実装は省略 */ }
}

余りがないものと仮定し、スライスをN要素配列のスライスに分割する。

これはas_flattenedの逆の操作である。

これはunsafeであるため、代わりにas_chunksas_rchunksの使用を検討すること。 例えばif let (chunks, []) = slice.as_chunks()let (chunks, []) = slice.as_chunks() else { unreachable!() };のように書ける。

安全性

この関数を呼び出せるのは次の場合のみである。 - スライスがN要素ごとにちょうど分割できる(つまりself.len() % N == 0) - N != 0

サンプル

let slice: &[char] = &['l', 'o', 'r', 'e', 'm', '!'];
let chunks: &[[char; 1]] =
    // 安全性:1要素ごとの分割なら余りが出ることはない
    unsafe { slice.as_chunks_unchecked() };
assert_eq!(chunks, &[['l'], ['o'], ['r'], ['e'], ['m'], ['!']]);
let chunks: &[[char; 3]] =
    // 安全性:スライス長(6)は3の倍数
    unsafe { slice.as_chunks_unchecked() };
assert_eq!(chunks, &[['l', 'o', 'r'], ['e', 'm', '!']]);

// これらは不良動作(unsound)
// let chunks: &[[_; 5]] = slice.as_chunks_unchecked() // スライス長が5の倍数ではない
// let chunks: &[[_; 0]] = slice.as_chunks_unchecked() // 長さゼロの配列塊は許されない

<[T]>::as_chunks_unchecked_mut

原典

impl<T> [T] {
    #[stable(feature = "slice_as_chunks", since = "1.88.0")]
    #[rustc_const_stable(feature = "slice_as_chunks", since = "1.88.0")]
    #[inline]
    #[must_use]
    #[track_caller]
    pub const unsafe fn as_chunks_unchecked_mut<const N: usize>(&mut self) -> &mut [[T; N]]
    { /* 実装は省略 */ }
}

余りがないものと仮定し、スライスをN要素配列のスライスに分割する。

これはas_flattened_mutの逆の操作である。

これはunsafeであるため、代わりにas_chunks_mutas_rchunks_mutの使用を検討すること。 例えばif let (chunks, []) = slice.as_chunks_mut()let (chunks, []) = slice.as_chunks_mut() else { unreachable!() };のように書ける。

安全性

この関数を呼び出せるのは次の場合のみである。

  • スライスがN要素ごとにちょうど分割できる(つまりself.len() % N == 0
  • N != 0

サンプル

let slice: &mut [char] = &mut ['l', 'o', 'r', 'e', 'm', '!'];
let chunks: &mut [[char; 1]] =
    // 安全性:1要素ごとの分割なら余りが出ることはない
    unsafe { slice.as_chunks_unchecked_mut() };
chunks[0] = ['L'];
assert_eq!(chunks, &[['L'], ['o'], ['r'], ['e'], ['m'], ['!']]);
let chunks: &mut [[char; 3]] =
    // 安全性:スライス長(6)は3の倍数
    unsafe { slice.as_chunks_unchecked_mut() };
chunks[1] = ['a', 'x', '?'];
assert_eq!(slice, &['L', 'o', 'r', 'a', 'x', '?']);

// これらは不良動作
// let chunks: &[[_; 5]] = slice.as_chunks_unchecked_mut() // スライス長が5の倍数ではない
// let chunks: &[[_; 0]] = slice.as_chunks_unchecked_mut() // 長さゼロの配列塊は許されない

<[T]>::as_rchunks

原典

impl<T> [T] {
    #[stable(feature = "slice_as_chunks", since = "1.88.0")]
    #[rustc_const_stable(feature = "slice_as_chunks", since = "1.88.0")]
    #[inline]
    #[track_caller]
    #[must_use]
    pub const fn as_rchunks<const N: usize>(&self) -> (&[T], &[[T; N]])
    { /* 実装は省略 */ }
}

スライスを末尾からN要素配列のスライスに分割し、長さがN未満の余りスライスを返す。

余りは割り算の余りとしての意味がある。let (remainder, chunks) = slice.as_rchunks()とした場合、次が成り立つ。

  • remainder.len()slice.len() % Nに等しい
  • chunks.len()slice.len() / Nに等しい
  • slice.len()chunks.len() * N + remainder.len()に等しい

配列塊はas_flattenedにより再びTのスライスに戻すことができる。

パニック

Nがゼロの場合はパニックする。

この検査は、実行時値ではなく定数の総称引数に対して行われるため、 特定の単一化(monomorphization)では常にパニックし、それ以外では決してパニックしない。

サンプル

let slice = ['l', 'o', 'r', 'e', 'm'];
let (remainder, chunks) = slice.as_rchunks();
assert_eq!(remainder, &['l']);
assert_eq!(chunks, &[['o', 'r'], ['e', 'm']]);

<[T]>::as_rchunks_mut

原典

impl<T> [T] {
    #[stable(feature = "slice_as_chunks", since = "1.88.0")]
    #[rustc_const_stable(feature = "slice_as_chunks", since = "1.88.0")]
    #[inline]
    #[track_caller]
    #[must_use]
    pub const fn as_rchunks_mut<const N: usize>(&mut self) -> (&mut [T], &mut [[T; N]])
    { /* 実装は省略 */ }
}

スライスを末尾からN要素配列のスライスに分割し、長さがN未満の余りスライスを返す。

余りは割り算の余りとしての意味がある。let (remainder, chunks) = slice.as_rchunks_mut()とした場合、次が成り立つ。

  • remainder.len()slice.len() % Nに等しい
  • chunks.len()slice.len() / Nに等しい
  • slice.len()chunks.len() * N + remainder.len()に等しい

配列塊はas_flattened_mutにより再びTのスライスに戻すことができる。

パニック

Nがゼロの場合はパニックする。

この検査は、実行時値ではなく定数の総称引数に対して行われるため、 特定の単一化(monomorphization)では常にパニックし、それ以外では決してパニックしない。

サンプル

let v = &mut [0, 0, 0, 0, 0];
let mut count = 1;

let (remainder, chunks) = v.as_rchunks_mut();
remainder[0] = 9;
for chunk in chunks {
    *chunk = [count; 2];
    count += 1;
}
assert_eq!(v, &[9, 1, 1, 2, 2]);

変更点リスト

言語

コンパイラ

プラットフォーム対応

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

ライブラリ

安定化されたAPI

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

Cargo

Rustdoc

互換性メモ

関連リンク

さいごに

次のリリースのRust 1.89は8/8(金)にリリースされる予定です。 Rust 1.89では配列の要素数_で推論できるようになったり、format_args!()が変数に入るようになったりするようです。

ライセンス表記

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



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

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