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


Rust 1.85を早めに深掘り

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

ピックアップ

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

Rust 2024が使えるようになった

2025年も2ヶ月が過ぎようとしていますが、ついにRust 2024がやってまいりました。

全部をまとめるのには時間が足りなかったので気になったものだけざっくり抜粋します。 あとで追記するかも(誰かが記事書いてくれればリンクするかも)。

  • impl Traitでライフタイムをちゃんと記述出来るように
  • FutureIntoFutureuseなしで使えるように
  • Box<[T]>IntoIteratorを実装
  • Cargo.tomlでのoptional = trueな依存クレートが勝手にfeatureにならないように

非同期クロージャが使えるようになった

async || 0のような文法でクロージャを非同期化できるようになりました。 これはエディションに関係なく導入されたため、非同期が使えるRust 2018以降ならどのエディションでも使えます。

これまでもクロージャから非同期ブロックを返す|| async { 0 }のような方法もありましたが、 主にライフタイム周りで不可能なことも多くありました。

今回導入された非同期クロージャによってそれらの問題が解消され、非同期関数を受け取る関数を書きやすくなったり、 非同期処理をクロージャで書きやすくなったりします。

同時に非同期関数を表すトレイトAsyncFnOnceAsyncFnMutAsyncFnも導入されました。 これらはFn一家と同様、通常のトレイトとは少し異なりAsyncFn(T) -> Uのような文法で使います。

// こんな簡単な関数を書くのもこれまでは難しかった
async fn handle_requests<F>(f: F)
where
    F: AsyncFn(&Request) -> Response,
{
    while let Some(req) = recv().await {
        let res = f(&req).await;
        send(res).await;
    }
}

async fn run() {
    handle_requests(async |_req| {
        Response
    });
}

async fn recv() -> Option<Request> { Some(Request) }
async fn send(res: Response) {}

struct Request;
struct Response;

浮動小数点数型の各メソッドを定数に使えるようになった

f32::absf32::to_radiansなど多くのメソッドが定数文脈で使えるようになりました。 定数の定義が分かりやすくなってとても便利です。

// Rust 1.84までもべた書きすれば定数にはできたが読みにくかった(あるいは自分でマクロを書く必要があった)
const FRONT: std::ops::Range<f32> = -90. * std::f32::consts::PI / 180.0..90. * std::f32::consts::PI / 180.0;
// Rust 1.85からは普通にメソッドを使える
const FRONT: std::ops::Range<f32> = f32::to_radians(-90.)..f32::to_radians(90.);

unzipの代わりにcollectを使えるようになった

タプルを返すイテレータを分解しつつVecに格納するにはIterator::unzipを使っていましたが、 2要素でしか使えなかったり、Resultと一緒に使えなかったりと地味に不便でした。

Rust 1.85では1~12要素までのタプルでcollectを使って同じことができるようになりました。

fn main() -> Result<(), &'static str> {
    let csv = r#"
1,2,3
4,5,6
    "#
    .trim();

    let (first, second, third): (Vec<_>, Vec<_>, Vec<_>) = csv
        .lines()
        .map(|line| {
            let mut iter = line
                .split(',')
                .map(|s| s.parse::<u32>().map_err(|_| "parse error"));
            Ok((
                iter.next().ok_or("missing first")??,
                iter.next().ok_or("missing second")??,
                iter.next().ok_or("missing third")??,
            ))
        })
        .collect::<Result<_, _>>()?;

    assert_eq!(first, [1, 4]);
    assert_eq!(second, [2, 5]);
    assert_eq!(third, [3, 6]);

    Ok(())
}

また似たようなことをextendでも出来るようになっています。

fn main() {
    let mut vecs = (Vec::new(), Vec::new());

    vecs.extend([(0, 1), (2, 3)]);

    let (a, b) = vecs;
    assert_eq!(a, [0, 2]);
    assert_eq!(b, [1, 3]);
}

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

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

BuildHasherDefault::new

原典

impl<H> BuildHasherDefault<H> {
    #[stable(feature = "build_hasher_default_const_new", since = "1.85.0")]
    #[rustc_const_stable(feature = "build_hasher_default_const_new", since = "1.85.0")]
    pub const fn new() -> Self
    { /* 実装は省略 */ }
}

ハッシャーH向けの新しいBuildHasherDefaultを生成する。

ptr::fn_addr_eq

原典

#[stable(feature = "ptr_fn_addr_eq", since = "1.85.0")]
#[inline(always)]
#[must_use = "function pointer comparison produces a value"]
pub fn fn_addr_eq<T: FnPtr, U: FnPtr>(f: T, g: U) -> bool
{ /* 実装は省略 */ }

二つの関数ポインタのアドレスが等しいかどうかを比較する。

これはf == gと同じだが、この関数を使うことで関数ポインタの比較に関する潜在的に意外な意味論が関与していることが明確になる。

関数がどのようにコンパイルされるかについてはほとんど保証されておらず、関数には本質的な“識別子”がない。 特に次のような比較で顕著である。

  • 関数が同等である場合、思いもよらずtrueを返す可能性がある。

    例えば、次のプログラムは最適化されている場合に(true, true)を出力する可能性が高い(ただし保証されているわけではない)。

    let f: fn(i32) -> i32 = |x| x;
    let g: fn(i32) -> i32 = |x| x + 0;  // 異なるクロージャ、異なる本体
    let h: fn(u32) -> u32 = |x| x + 0;  // 異なるシグネチャでも
    dbg!(std::ptr::fn_addr_eq(f, g), std::ptr::fn_addr_eq(f, h)); // 等しい保証はない
    
  • どのような場合でもfalseを返す可能性がある。

    これは特に総称関数で起こりやすいものの、あらゆる関数で起こり得る (実装の観点からは、関数がコンパイラによって複数回処理された結果機械語が重複することがあるため)。

こういった偽陽性偽陰性があるにもかかわらず、この比較は依然として有用である。 具体的には以下の条件を満たす場合。

  • TUと同じ型であるか、TU部分型であるか、UT部分型である場合、かつ
  • ptr::fn_addr_eq(f, g)trueを返す場合

fを呼び出すこととgを呼び出すことは同等である。

サンプル

use std::ptr;

fn a() { println!("a"); }
fn b() { println!("b"); }
assert!(!ptr::fn_addr_eq(a as fn(), b as fn()));

io::ErrorKind::QuotaExceeded

原典

pub enum ErrorKind {
    // ...
    #[stable(feature = "io_error_quota_exceeded", since = "1.85.0")]
    QuotaExceeded,
}

ファイルシステムのクォータまたは他の種類のクォータが超過した。

io::ErrorKind::CrossesDevices

原典

pub enum ErrorKind {
    // ...
    #[stable(feature = "io_error_crosses_devices", since = "1.85.0")]
    CrossesDevices,
}

バイスまたはファイルシステムをまたぐ(ハード)リンクまたは名前変更。

{float}::midpoint

原典

impl f32 {
    #[inline]
    #[stable(feature = "num_midpoint", since = "1.85.0")]
    #[rustc_const_stable(feature = "num_midpoint", since = "1.85.0")]
    pub const fn midpoint(self, other: f32) -> f32
    { /* 実装は省略 */ }
}

selfrhsの中間点を計算する。

この関数はどちらかの引数がNaNの場合、または+infと-infの組み合わせが引数として提供された場合にNaNを返す。

サンプル

assert_eq!(1f32.midpoint(4.0), 2.5);
assert_eq!((-5.5f32).midpoint(8.0), 1.25);

{unsigned integer}::midpoint

原典

impl u64 {
    #[stable(feature = "num_midpoint", since = "1.85.0")]
    #[rustc_const_stable(feature = "num_midpoint", since = "1.85.0")]
    #[must_use = "this returns the result of the operation, \
                  without modifying the original"]
    #[inline]
    pub const fn midpoint(self, rhs: u64) -> u64
    { /* 実装は省略 */ }
}

selfrhsの中間点を計算する。

midpoint(a, b)(a + b) / 2を、十分大きな符号なし整数型で実行したかのように扱う。 つまり結果は常にゼロに丸められ、オーバーフローは決して発生しない。

サンプル

assert_eq!(0u64.midpoint(4), 2);
assert_eq!(1u64.midpoint(4), 2);

NonZeroU*::midpoint

原典

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

selfrhsの中間点を計算する。

midpoint(a, b)(a + b) >> 1を、十分大きな符号付き整数型で実行したかのように扱う。 つまり結果は常に負の無限大に丸められ、オーバーフローは決して発生しない。

サンプル

let one = NonZero::new(1u32)?;
let two = NonZero::new(2u32)?;
let four = NonZero::new(4u32)?;

assert_eq!(one.midpoint(four), two);
assert_eq!(four.midpoint(one), two);
use std::num::NonZero;

fn main() { test().unwrap(); }
fn test() -> Option<()> {
let one = NonZero::new(1u32)?;
let two = NonZero::new(2u32)?;
let four = NonZero::new(4u32)?;

assert_eq!(one.midpoint(four), two);
assert_eq!(four.midpoint(one), two);
Some(())
}

std::task::Waker::noop

原典

impl Waker {
    #[inline]
    #[must_use]
    #[stable(feature = "noop_waker", since = "1.85.0")]
    #[rustc_const_stable(feature = "noop_waker", since = "1.85.0")]
    pub const fn noop() -> &'static Waker
    { /* 実装は省略 */ }
}

使われたとき何もしないWakerへの参照を返す。

これは主に、いくつかのFutureをポーリングするためにContextが必要なテストを書く際に有用であるが、 それらのFutureがWakerを起こすことを期待していない場合や、何かが発生した場合に何もする必要がない場合に使われる。

より一般的には、FutureをポーリングするのにWaker::noop()を使うことは再度Futureをポーリングすべき時期の通知を破棄することとなる。 そのため、Futureが進行するためにその通知が必要ない場合にのみ使うべきである。

所有されたWakerが必要な場合はこのWakerclone()すること。

サンプル

use std::future::Future;
use std::task;

let mut cx = task::Context::from_waker(task::Waker::noop());

let mut future = Box::pin(async { 10 });
assert_eq!(future.as_mut().poll(&mut cx), task::Poll::Ready(10));

変更点リスト

言語

コンパイラ

プラットフォーム対応

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

ライブラリ

安定化されたAPI

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

Cargo

Rustdoc

互換性メモ

内部の変更

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

関連リンク

さいごに

次のリリースのRust 1.86は4/4(金)にリリースされる予定です。 Rust 1.86では1つの配列から複数の要素を可変参照で取り出せるようになったり、トレイトをアップキャストできるようになったりするようです。

ライセンス表記

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



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

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