本日12/29(金)にリリースされたRust 1.75の変更点を詳しく紹介します。 もしこの記事が参考になれば記事末尾から活動を支援頂けると嬉しいです。
- ピックアップ
- 最近のrust-analyzer
- 安定化されたAPIのドキュメント
- Atomic*::from_ptr
- FileTimes
- FileTimesExt
- File::set_modified
- File::set_times
- IpAddr::to_canonical
- Ipv6Addr::to_canonical
- Option::as_slice
- Option::as_mut_slice
- pointer::byte_add
- pointer::byte_offset
- pointer::byte_offset_from
- pointer::byte_sub
- pointer::wrapping_byte_add
- pointer::wrapping_byte_offset
- pointer::wrapping_byte_sub
- 変更点リスト
- 関連リンク
- さいごに
- ライセンス表記
ピックアップ
個人的に注目する変更点を「ピックアップ」としてまとめました。 全ての変更点を網羅したリストは変更点リストをご覧ください。
トレイトで非同期関数の定義や戻り値でのimpl Traitを使えるようになった
2019年11月8日にリリースされたRust 1.39で非同期プログラミングができるようになってから4年超、ついにこの時がやってまいりました。
これまで、トレイトに非同期関数を定義するときはasync_traitなどを使って定義していました。
しかしこれは定義を変換するもので、戻り値がPin<Box<dyn Future + Send + Sync>>
となってしまう、つまりヒープ使う非効率なものでした。
Rust 1.75からは言語機能としてトレイト内でのでの非同期関数の定義、及び戻り値でのimpl Trait
の使用に対応し、
ヒープを使わずに非同期関数を定義できるようになりました。
なおasync fn() -> T
はfn() -> 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)) }
安全性
ptr
はalign_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
に変換し、それ以外の場合はself
をIpAddr::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
なポインタの対象においては、この操作はデータポインタのみを変更し、メタデータは変更されない。
変更点リスト
言語
- トレイト中における
async fn
及び戻り値でのimpl Trait
を安定化 - 定数文脈における、
&mut T
を含んだ関数ポインタのシグネチャを許容 usize
・isize
を半開区間で網羅的にマッチchar
がu32
と同じサイズ及びアライメントであることを保証- NULLポインタがアドレスとして0であることを文書化
match
で値を部分的にムーブするのを許容- 32ビットのx86ターゲットにおける浮動小数点の非準拠な挙動について注釈を追加
- RISC-Vの批准されたターゲット機能を安定化
コンパイラ
- 負の一貫性を再設計し、部分的にしか重複しない実装を適切に考慮
COINDUCTIVE_OVERLAP_IN_COHERENCE
を拒否に変更し、依存関係では警告を出力- NLLでの活性度(liveness)計算において、別名の境界を考慮
riscv64-linux-android
ターゲットの仕様にV(vector)拡張を追加- 小さい関数ではクレートを跨ぐインライン化を自動で有効化
- Tier 3ターゲットをいくつか追加
Rustのティア付けされたプラットフォーム対応の詳細はPlatform Supportのページ(※訳注:英語)を参照
ライブラリ
Waker
を不必要に複製しないようWaker::clone_from
をオーバーライドVecDeque<u8>
がBufRead
を実装するようになった- 内部のイテレーターが
FusedIterator
を実装しているとき、DecodeUtf16
もそれを実装するようになった - IPアドレスが
Not, Bit{And,Or}{,Assign}
を実装するようになった ExitCode
がDefault
を実装するようになった- NPO(NULLポインタ最適化)におけるNoneの表現を保証
- 原子的な読み込みが必ず読み取り専用であることが保証される場合を文書化
- 再帰的なTLSの初期化の結果を拡大する
- Windows:ミリ秒以下のsleepに対応
str::SplitInclusive
におけるDoubleEndedIterator
実装のジェネリクス境界を修正- UNIXでない
cfg(unix)
環境での終了ステータス・待機ステータスを修正
安定化されたAPI
Atomic*::from_ptr
FileTimes
FileTimesExt
File::set_modified
File::set_times
IpAddr::to_canonical
Ipv6Addr::to_canonical
Option::as_slice
Option::as_mut_slice
pointer::byte_add
pointer::byte_offset
pointer::byte_offset_from
pointer::byte_sub
pointer::wrapping_byte_add
pointer::wrapping_byte_offset
pointer::wrapping_byte_sub
以下のAPIが定数文脈で使えるようになった。
Ipv6Addr::to_ipv4_mapped
MaybeUninit::assume_init_read
MaybeUninit::zeroed
mem::discriminant
mem::zeroed
Cargo
Rustdoc
- rustdocでの無効なRustコードの受け入れを減少
- 影響を受けるトレイトにおいてオブジェクト安全性の欠如を文書化
#[repr(transparent)]
が公開ABIの一部でない場合、それらを非表示- 列挙型がCのようなバリアントを持つとき、その判別子(discriminant)を表示
互換性メモ
- FreeBSDターゲットには最低バージョン12が必要
- 公式にMIPSターゲットをTier 2からTier 3に降格
- 定数文脈においてアライメントのずれをコンパイルエラー化
- 詰め込まれたサイズのないフィールドへの参照の検出を修正
- コンパイラプラグインへの対応を削除
内部の変更
これらの変更がユーザーに直接利益をもたらすわけではないものの、コンパイラ及び周辺ツール内部には重要なパフォーマンス改善をもたらす。
librustc_driver.so
をBOLTで最適化- 開発版及びNightly版のrustcで並列化されたフロントエンドを有効化
- Nightlyチャンネルでrustupコンポーネントとして
rustc-codegen-cranelift
を配布
関連リンク
さいごに
次のリリースのRust 1.76は2024/2/8(金)にリリースされる予定です。 Rust 1.76ではNULL終端するC文字列リテラルが使えるようになったり、子トレイトを親トレイトに型強制できるようになったりする予定です。