あずんひの日

あずんひの色々を書き留めるブログ

Rust 1.75を早めに深掘り

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

ピックアップ

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

トレイトで非同期関数の定義や戻り値でのimpl Traitを使えるようになった

2019年11月8日にリリースされたRust 1.39で非同期プログラミングができるようになってから4年超、ついにこの時がやってまいりました。

これまで、トレイトに非同期関数を定義するときはasync_traitなどを使って定義していました。 しかしこれは定義を変換するもので、戻り値がPin<Box<dyn Future + Send + Sync>>となってしまう、つまりヒープ使う非効率なものでした。

Rust 1.75からは言語機能としてトレイト内でのでの非同期関数の定義、及び戻り値でのimpl Traitの使用に対応し、 ヒープを使わずに非同期関数を定義できるようになりました。

なおasync fn() -> Tfn() -> impl Future<Output = T>の糖衣構文であるため、定義と実装で混ぜて使うこともできます。

use std::future::Future;
use std::fmt::Display;

pub trait Hoge {
    // どちらも同じ意味
    async fn yattane(&self) -> u32;
    fn yattaze(&self) -> impl Future<Output = u32>;

    // impl Traitの使い道は非同期だけじゃない
    fn display(&self) -> impl Display;
}

impl Hoge for u32 {
    // 定義ではasync fnだけど実装ではimpl Traitも使える
    fn yattane(&self) -> impl Future<Output = u32> {
        async { 42 }
    }

    // 定義ではimpl Traitだけど実装ではasync fnも使える
    async fn yattaze(&self) -> u32 {
        1_75
    }

    fn display(&self) -> impl Display {
        std::f32::consts::PI
    }
}

ただしここで警告が出ていることに注意してください(pub traitではなくtraitで定義した場合、この警告は出ません)。

warning: use of `async fn` in public traits is discouraged as auto trait bounds cannot be specified
 --> src/lib.rs:6:5
  |
6 |     async fn yattane(&self) -> u32;
  |     ^^^^^

「公開トレイトでasync fnを使用するのは自動トレイトの境界が指定できないため推奨されない」という意味です。

一般的には非同期関数はtokio::spawnなどで起動するわけですが、効率化のためタスクは別スレッドで実行され得ることから、 これら起動用関数はSend境界を要求することがほとんどです。 しかしasync fnによる定義ではSend境界の有無を指定できないため、impl Futureによる定義が推奨されているのです。

use std::future::Future;

pub trait Fuga: Send {
    async fn async_fn(&self);
    fn impl_trait(&self) -> impl Future<Output = ()> + Send;
}

// こちらはエラーだが・・・
fn spawn_async_fn<T: Fuga + 'static>(v: T) {
    tokio::spawn(async move {
        v.async_fn().await;
    });
}

// こちらは通る
fn spawn_impl_trait<T: Fuga + 'static>(v: T) {
    tokio::spawn(async move {
        v.impl_trait().await;
    });
}

async fnであっても境界を指定できる機能は現在検討中のようです。気長に待ちましょう。

Cargo.tomlのバージョンを省略できるようになった

内部でしか使わないクレートでは[package] version = "0.0.0"と指定しているプロジェクトも多いのではないでしょうか。

Rust 1.75ではversionフィールドを省略することで、version = "0.0.0"と同時にpublish = falseが指定されたと見なされるようになりました。 publishの指定の手間が減るのはありがたいですね。

ポインタをバイト単位で操作できるようになった

*mut Tなポインタの位置をバイト単位で変更する場合、(ptr as *mut u8).add(1) as *mut Tのようにしていました。 Rust 1.75からはptr.byte_add(1)とすることで、バイト単位での位置変更ができるようになりました。

今回使えるようになったメソッドは下記の通りです。 - byte_add - byte_offset - byte_offset_from - byte_sub - wrapping_byte_add - wrapping_byte_offset - wrapping_byte_sub

もちろん、アライメントが揃っていないポインタを読み出そうとすると未定義動作を引き起こすため注意してください。

最近のrust-analyzer

最近rust-analyzerに入った変更の中から個人的に気になったものをピックアップしました。

フォーマット文字列中の変数が認識されるようになった

2023-12-11 (v0.3.1766)での変更です。

format!("{x}")のようにフォーマット文字列で変数を使っているときでも、その文字列内の変数が変数として認識されるようになりました。 これにより、変数の情報を表示したり名前の変更ができるようになったりします。

フォーマット文字列中で変数の情報を表示した状態
フォーマット文字列中で変数の情報を表示した状態

補完候補が文脈に沿って並べ替わるようになった

2023-12-11 (v0.3.1766)での変更です。

変数や型の補完で、入力中の場面に適したものほど優先的に候補へ出てくるようになりました。 先頭が同じ名前の変数が複数あっても、要求される型に適した候補を選択しやすくなります。

型に沿って候補が並び変わる様子
型に沿って候補が並び変わる様子

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

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

Atomic*::from_ptr

原典

impl AtomicUsize {
    #[stable(feature = "atomic_from_ptr", since = "1.75.0")]
    #[rustc_const_unstable(feature = "const_atomic_from_ptr", issue = "108652")]
    pub const unsafe fn from_ptr<'a>(ptr: *mut usize) -> &'a AtomicUsize
    { /* 実装は省略 */ }
}

ポインタから原子的整数への参照を生成する。

サンプル

#![feature(pointer_is_aligned)]
use std::sync::atomic::{self, AtomicUsize};
use std::mem::align_of;

// 確保された値へのポインタを取得
let ptr: *mut usize = Box::into_raw(Box::new(0));

assert!(ptr.is_aligned_to(align_of::<AtomicUsize>()));

{
    // 確保された値への原子的なビューを生成する
    let atomic = unsafe { AtomicUsize::from_ptr(ptr) };

    // 原子的操作に`atomic`を使うことで他のスレッドと共有することもできる
    atomic.store(1, atomic::Ordering::Relaxed);
}

// 原始変数への参照の寿命は上記ブロック内で尽きているため、
// `ptr`を通した値に非原子的アクセスをしても問題ない
assert_eq!(unsafe { *ptr }, 1);

// 値を解放する
unsafe { drop(Box::from_raw(ptr)) }

安全性

  • ptralign_of::<AtomicUsize>()に揃えられてなければならない(環境によってはalign_of::<usize>()よりも大きくなる場合があることに注意)
  • ptrはライフタイム'a全体において読み書き両方で有効でなければならない
  • 原子的アクセスのメモリモデルを遵守する必要がある。特に原子的アクセスと非原子的アクセスを混在させたり、 異なる大きさへの同期しない原子的アクセスは許されない

FileTimes

原典

#[derive(Copy, Clone, Debug, Default)]
#[stable(feature = "file_set_times", since = "1.75.0")]
pub struct FileTimes(/* フィールドは省略 */);

ファイル上の様々なタイムスタンプを表す。

FileTimesExt

原典

#[stable(feature = "file_set_times", since = "1.75.0")]
pub trait FileTimesExt: Sealed {
    #[stable(feature = "file_set_times", since = "1.75.0")]
    fn set_created(self, t: SystemTime) -> Self;
}

fs::FileTimesへのWindows限定拡張。

File::set_modified

原典

impl File {
    #[stable(feature = "file_set_times", since = "1.75.0")]
    #[inline]
    pub fn set_modified(&self, time: SystemTime) -> io::Result<()>
    { /* 実装は省略 */ }
}

対象ファイルの更新日時を変更する。

これはset_times(FileTimes::new().set_modified(time))の略記である。

File::set_times

原典

impl File {
    #[stable(feature = "file_set_times", since = "1.75.0")]
    #[doc(alias = "futimens")]
    #[doc(alias = "futimes")]
    #[doc(alias = "SetFileTime")]
    pub fn set_times(&self, times: FileTimes) -> io::Result<()>
    { /* 実装は省略 */ }
}

対象ファイルの各種日時を変更する。

環境依存の挙動

この関数は現在、Unixではfutimens関数(macOS 10.13以前ではfutimesに後退)、 WindowsではSetFileTime関数に対応している。これは将来変更される可能性があることに注意されたい。

エラー

対象ファイルの各種日時を変更するための権限が不足しているとき、この関数はエラーを返す。 またその他OS固有の不特定な条件下でもエラーを返すことがある。

この関数はOSがFileTimes構造体に設定されている1つ以上の日時変更に対応していない場合にもエラーを返すことがある。

サンプル

fn main() -> std::io::Result<()> {
    use std::fs::{self, File, FileTimes};

    let src = fs::metadata("src")?;
    let dest = File::options().write(true).open("dest")?;
    let times = FileTimes::new()
        .set_accessed(src.accessed()?)
        .set_modified(src.modified()?);
    dest.set_times(times)?;
    Ok(())
}

IpAddr::to_canonical

原典

impl IpAddr {
    #[inline]
    #[must_use = "this returns the result of the operation, \
                  without modifying the original"]
    #[stable(feature = "ip_to_canonical", since = "1.75.0")]
    #[rustc_const_stable(feature = "ip_to_canonical", since = "1.75.0")]
    pub const fn to_canonical(&self) -> IpAddr
    { /* 実装は省略 */ }
}

このアドレスがIPv4に射影されたIPv6アドレスである場合はIpAddr::V4に変換し、それ以外の場合はselfをそのまま返す。

サンプル

use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};

let localhost_v4 = Ipv4Addr::new(127, 0, 0, 1);

assert_eq!(IpAddr::V4(localhost_v4).to_canonical(), localhost_v4);
assert_eq!(IpAddr::V6(localhost_v4.to_ipv6_mapped()).to_canonical(), localhost_v4);
assert_eq!(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)).to_canonical().is_loopback(), true);
assert_eq!(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0x7f00, 0x1)).is_loopback(), false);
assert_eq!(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0x7f00, 0x1)).to_canonical().is_loopback(), true);

Ipv6Addr::to_canonical

原典

impl Ipv6Addr {
    #[inline]
    #[must_use = "this returns the result of the operation, \
                  without modifying the original"]
    #[stable(feature = "ip_to_canonical", since = "1.75.0")]
    #[rustc_const_stable(feature = "ip_to_canonical", since = "1.75.0")]
    pub const fn to_canonical(&self) -> IpAddr
    { /* 実装は省略 */ }
}

このアドレスがIPv4に射影されたアドレスである場合はIpAddr::V4に変換し、それ以外の場合はselfIpAddr::V6に包んで返す。

サンプル

#![feature(ip)]
use std::net::Ipv6Addr;

assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0x7f00, 0x1).is_loopback(), false);
assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0x7f00, 0x1).to_canonical().is_loopback(), true);

Option::as_slice

原典

impl<T> Option<T> {
    #[inline]
    #[must_use]
    #[stable(feature = "option_as_slice", since = "1.75.0")]
    pub fn as_slice(&self) -> &[T]
    { /* 実装は省略 */ }
}

値がある場合はその値へのスライスを返す。Noneの場合は空のスライスが返される。 このメソッドは、Optionまたはスライスに対する単一の型によるイテレーターを使うのに便利である。

注:Option<&T>に対してTへのスライスを得たい場合、opt.map_or(&[], std::slice::from_ref)によって値を展開することができる。

サンプル

assert_eq!(
    [Some(1234).as_slice(), None.as_slice()],
    [&[1234][..], &[][..]],
);

この関数の(借用を考慮しない)逆は[_]::firstである。

for i in [Some(1234_u16), None] {
    assert_eq!(i.as_ref(), i.as_slice().first());
}

Option::as_mut_slice

原典

    #[inline]
    #[must_use]
    #[stable(feature = "option_as_slice", since = "1.75.0")]
    pub fn as_mut_slice(&mut self) -> &mut [T]
    { /* 実装は省略 */ }
}

値がある場合はその値への可変スライスを返す。Noneの場合は空のスライスが返される。 このメソッドは、Optionまたはスライスに対する単一の型によるイテレーターを使うのに便利である。

注:Option<&mut T>に対してTへの可変スライスを得たい場合、opt.map_or(&mut [], std::slice::from_mut)によって値を展開することができる。

サンプル

assert_eq!(
    [Some(1234).as_mut_slice(), None.as_mut_slice()],
    [&mut [1234][..], &mut [][..]],
);

戻り値は元のOptionを指す、0または1要素の可変スライスである。

let mut x = Some(1234);
x.as_mut_slice()[0] += 1;
assert_eq!(x, Some(1235));

この関数の(借用を考慮しない)逆は[_]::first_mutである。

assert_eq!(Some(123).as_mut_slice().first_mut(), Some(&mut 123))

pointer::byte_add

原典

impl<T: ?Sized> *const T {
    #[must_use]
    #[inline(always)]
    #[stable(feature = "pointer_byte_offsets", since = "1.75.0")]
    #[rustc_const_stable(feature = "const_pointer_byte_offsets", since = "1.75.0")]
    #[rustc_allow_const_fn_unstable(set_ptr_value)]
    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
    pub const unsafe fn byte_add(self, count: usize) -> Self
    { /* 実装は省略 */ }
}

ポインタからのオフセットをバイト単位で計算する(.byte_offset(count as isize)への利便性)。

countはバイト単位である。

これはu8ポインタにキャストして[add]することへの純粋な補助である。 文書と安全性の要件については上記メソッドを参照されたい。

Sizedなポインタの対象においては、この操作はデータポインタのみを変更し、メタデータは変更されない。

pointer::byte_offset

原典

impl<T: ?Sized> *const T {
    #[must_use]
    #[inline(always)]
    #[stable(feature = "pointer_byte_offsets", since = "1.75.0")]
    #[rustc_const_stable(feature = "const_pointer_byte_offsets", since = "1.75.0")]
    #[rustc_allow_const_fn_unstable(set_ptr_value)]
    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
    pub const unsafe fn byte_offset(self, count: isize) -> Self
    { /* 実装は省略 */ }
}

ポインタからのオフセットをバイト単位で計算する。

countバイト単位である。

これはu8ポインタにキャストしてoffsetすることへの純粋な補助である。 文書と安全性の要件については上記メソッドを参照されたい。

Sizedなポインタの対象においては、この操作はデータポインタのみを変更し、メタデータは変更されない。

pointer::byte_offset_from

原典

impl<T: ?Sized> *const T {
    #[inline(always)]
    #[stable(feature = "pointer_byte_offsets", since = "1.75.0")]
    #[rustc_const_stable(feature = "const_pointer_byte_offsets", since = "1.75.0")]
    #[rustc_allow_const_fn_unstable(set_ptr_value)]
    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
    pub const unsafe fn byte_offset_from<U: ?Sized>(self, origin: *const U) -> isize
    { /* 実装は省略 */ }
}

2つのポインタ間の距離を計算する。戻り値はバイト単位である。

これはu8ポインタにキャストしてoffset_fromすることへの純粋な補助である。 文書と安全性の要件については上記メソッドを参照されたい。

Sizedなポインタの対象においては、この操作はデータポインタのみを考慮し、メタデータは無視する。

pointer::byte_sub

原典

impl<T: ?Sized> *const T {
    #[must_use]
    #[inline(always)]
    #[stable(feature = "pointer_byte_offsets", since = "1.75.0")]
    #[rustc_const_stable(feature = "const_pointer_byte_offsets", since = "1.75.0")]
    #[rustc_allow_const_fn_unstable(set_ptr_value)]
    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
    pub const unsafe fn byte_sub(self, count: usize) -> Self
    { /* 実装は省略 */ }
}

ポインタからのオフセットをバイト単位で計算する(.byte_offset((count as isize).wrapping_neg())への利便性)。

countはバイト単位である。

これはu8ポインタにキャストしてsubすることへの純粋な補助である。 文書と安全性の要件については上記メソッドを参照されたい。

Sizedなポインタの対象においては、この操作はデータポインタのみを変更し、メタデータは変更されない。

pointer::wrapping_byte_add

原典

impl<T: ?Sized> *const T {
    #[must_use]
    #[inline(always)]
    #[stable(feature = "pointer_byte_offsets", since = "1.75.0")]
    #[rustc_const_stable(feature = "const_pointer_byte_offsets", since = "1.75.0")]
    #[rustc_allow_const_fn_unstable(set_ptr_value)]
    pub const fn wrapping_byte_add(self, count: usize) -> Self
    { /* 実装は省略 */ }
}

ポインタからのオフセットを折り返す算術によりバイト単位で計算する(.wrapping_byte_offset(count as isize)への利便性)。

countはバイト単位である。

これはu8ポインタにキャストしてwrapping_addすることへの純粋な補助である。 文書と安全性の要件については上記メソッドを参照されたい。

Sizedなポインタの対象においては、この操作はデータポインタのみを変更し、メタデータは変更されない。

pointer::wrapping_byte_offset

原典

impl<T: ?Sized> *const T {
    #[must_use]
    #[inline(always)]
    #[stable(feature = "pointer_byte_offsets", since = "1.75.0")]
    #[rustc_const_stable(feature = "const_pointer_byte_offsets", since = "1.75.0")]
    #[rustc_allow_const_fn_unstable(set_ptr_value)]
    pub const fn wrapping_byte_offset(self, count: isize) -> Self
    { /* 実装は省略 */ }
}

ポインタからのオフセットを折り返す算術によりバイト単位で計算する。

countはバイト単位である。

これはu8ポインタにキャストしてwrapping_offsetすることへの純粋な補助である。 文書と安全性の要件については上記メソッドを参照されたい。

Sizedなポインタの対象においては、この操作はデータポインタのみを変更し、メタデータは変更されない。

pointer::wrapping_byte_sub

原典

impl<T: ?Sized> *const T {
    #[must_use]
    #[inline(always)]
    #[stable(feature = "pointer_byte_offsets", since = "1.75.0")]
    #[rustc_const_stable(feature = "const_pointer_byte_offsets", since = "1.75.0")]
    #[rustc_allow_const_fn_unstable(set_ptr_value)]
    pub const fn wrapping_byte_sub(self, count: usize) -> Self
    { /* 実装は省略 */ }
}

ポインタからのオフセットを折り返す算術によりバイト単位で計算する(.wrapping_offset((count as isize).wrapping_neg())への利便性)。

countはバイト単位である。

これはu8ポインタにキャストしてwrapping_subすることへの純粋な補助である。 文書と安全性の要件については上記メソッドを参照されたい。

Sizedなポインタの対象においては、この操作はデータポインタのみを変更し、メタデータは変更されない。

変更点リスト

言語

コンパイラ

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

ライブラリ

安定化されたAPI

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

Cargo

Rustdoc

互換性メモ

内部の変更

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

関連リンク

さいごに

次のリリースのRust 1.76は2024/2/8(金)にリリースされる予定です。 Rust 1.76ではNULL終端するC文字列リテラルが使えるようになったり、子トレイトを親トレイトに型強制できるようになったりする予定です。

ライセンス表記

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