本日2/21(金)にリリースされたRust 1.85の変更点を詳しく紹介します。 もしこの記事が参考になれば記事末尾から活動を支援頂けると嬉しいです。
ピックアップ
個人的に注目する変更点を「ピックアップ」としてまとめました。 全ての変更点を網羅したリストは変更点リストをご覧ください。
Rust 2024が使えるようになった
2025年も2ヶ月が過ぎようとしていますが、ついにRust 2024がやってまいりました。
全部をまとめるのには時間が足りなかったので気になったものだけざっくり抜粋します。 あとで追記するかも(誰かが記事書いてくれればリンクするかも)。
impl Traitでライフタイムをちゃんと記述出来るようにFuture・IntoFutureがuseなしで使えるようにBox<[T]>がIntoIteratorを実装- Cargo.tomlでの
optional = trueな依存クレートが勝手にfeatureにならないように
非同期クロージャが使えるようになった
async || 0のような文法でクロージャを非同期化できるようになりました。
これはエディションに関係なく導入されたため、非同期が使えるRust 2018以降ならどのエディションでも使えます。
これまでもクロージャから非同期ブロックを返す|| async { 0 }のような方法もありましたが、
主にライフタイム周りで不可能なことも多くありました。
今回導入された非同期クロージャによってそれらの問題が解消され、非同期関数を受け取る関数を書きやすくなったり、 非同期処理をクロージャで書きやすくなったりします。
同時に非同期関数を表すトレイトAsyncFnOnce・AsyncFnMut・AsyncFnも導入されました。
これらは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::absやf32::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を返す可能性がある。これは特に総称関数で起こりやすいものの、あらゆる関数で起こり得る (実装の観点からは、関数がコンパイラによって複数回処理された結果機械語が重複することがあるため)。
こういった偽陽性と偽陰性があるにもかかわらず、この比較は依然として有用である。 具体的には以下の条件を満たす場合。
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 { /* 実装は省略 */ } }
selfとrhsの中間点を計算する。
この関数はどちらかの引数が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 { /* 実装は省略 */ } }
selfとrhsの中間点を計算する。
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 { /* 実装は省略 */ } }
selfとrhsの中間点を計算する。
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が必要な場合はこのWakerをclone()すること。
サンプル
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));
変更点リスト
言語
- 2024エディションが安定化。 詳細はエディションガイド(※訳注:英語)を参照
- 非同期クロージャの安定化。 詳細はRFC 3668(※訳注:英語)を参照
#[diagnostic::do_not_recommend]の安定化- 関数ポインタの比較に警告するリント
unpredictable_function_pointer_comparisonsの追加 #[no_mangle]と#[export_name]属性の組み合わせに対するリント
コンパイラ
プラットフォーム対応
Rustのティア付けされたプラットフォーム対応の詳細はPlatform Supportのページ(※訳注:英語)を参照
ライブラリ
- 標準ライブラリでのパニックにおいて、パスの先頭に
library/が付くようになった Windowsの
std::env::home_dir()で非標準の$HOME環境変数を無視するようになった次のリリースでこの関数の非推奨化が解除される予定。
- 全エディションで
AsyncFn*をpreludeに追加
安定化されたAPI
BuildHasherDefault::newptr::fn_addr_eqio::ErrorKind::QuotaExceededio::ErrorKind::CrossesDevices{float}::midpoint- Unsigned
{integer}::midpoint NonZeroU*::midpoint- impl
std::iter::Extendfor tuples with arity 1 through 12 FromIterator<(A, ...)>for tuples with arity 1 through 12std::task::Waker::noop
以下のAPIが定数文脈で使えるようになった。
mem::size_of_valmem::align_of_valLayout::for_valueLayout::align_toLayout::pad_to_alignLayout::extendLayout::arraystd::mem::swapstd::ptr::swapNonNull::newHashMap::with_hasherHashSet::with_hasherBuildHasherDefault::new<float>::recip<float>::to_degrees<float>::to_radians<float>::max<float>::min<float>::clamp<float>::abs<float>::signum<float>::copysignMaybeUninit::write
Cargo
Rustdoc
互換性メモ
rustcはtestcfgを既知のcfgとして扱わなくなった。 代わりにビルドシステムや--check-cfgの利用者が--check-cfg=cfg(test)を使って既知のcfgとして設定する必要がある。これはCargoのようなビルドシステムが条件付きで設定できるようにするためのものであり、 すべてのソースファイルがユニットテストに適しているわけではないため。 Cargoは(今のところ)無条件で
testcfgを既知のcfgとして設定する- 自明なwhere節と非自明なwhere節がある場合、潜在的に不正確な型推論を無効化
std::env::home_dir()は何年も前から非推奨となっていた。 これはWindowsにおいてHOME環境変数が設定されている場合に予期しない結果をもたらす可能性があったためである(これは通常ではない構成)。 この非標準な構成に依存しているコードとの互換性を懸念し、この関数の振る舞いは変更されていなかった。 この関数が非推奨となってから十分な時間が経過しているため、この関数の振る舞いを修正することになった。 次のリリースでこの関数の非推奨化が解除される予定である。core::ffi::c_charの符号をプラットフォーム既定のcharにより近づけるこれにより多くのティア2およびティア3のターゲット(主にArmおよびRISC-V組み込みターゲット)で
c_charがi8からu8、 またはその逆に変更された。この新しい定義によりコンパイルに失敗する可能性があるものの、Cとの互換性の問題が解消される。libcクレートは0.2.169リリースからこの変更に対応している。- 外部クレートからネストされた
macro_rulesマクロをコンパイルする際、内側のmacro_rulesの内容は(使用側ではなく)外部クレート側のエディションで構築されるようになった sparcv9-sun-solarisとx86_64-pc-solarisのSolarisベースラインを11.4に引き上げabi_unsupported_vector_typesリントを将来の破損報告に表示dyn Traitの複数の親トレイトをインスタンス化する際、指定すべき関連型を1つしか指定していない場合にエラーpowerpc64-ibm-aixの既定codemodelをlargeに変更
内部の変更
これらの変更がユーザーに直接利益をもたらすわけではないものの、コンパイラ及び周辺ツール内部では重要なパフォーマンス改善をもたらす。
関連リンク
さいごに
次のリリースのRust 1.86は4/4(金)にリリースされる予定です。 Rust 1.86では1つの配列から複数の要素を可変参照で取り出せるようになったり、トレイトをアップキャストできるようになったりするようです。