本日4/4(金)にリリースされたRust 1.86の変更点を詳しく紹介します。 もしこの記事が参考になれば記事末尾から活動を支援頂けると嬉しいです。
- ピックアップ
- 安定化されたAPIのドキュメント
- 変更点リスト
- 関連リンク
- さいごに
- ライセンス表記
ピックアップ
個人的に注目する変更点を「ピックアップ」としてまとめました。 全ての変更点を網羅したリストは変更点リストをご覧ください。
トレイトをアップキャストできるようになった
これまで、&dyn TraitやBox<dyn Trait>などトレイトオブジェクトを親トレイトのオブジェクトに変換することはできませんでした。
例えばErrorトレイトは祖先にDebug・Displayといったトレイトを持ちます。
ここで&dyn Errorなオブジェクトを使って(ToStringトレイトを間に挟む)to_stringメソッドを呼び出すことはできます。
しかしこのオブジェクトを&dyn Displayなオブジェクトとして渡すことはできませんでした。
回避策として独自トレイトを定義して変換することもできましたが、 Rust 1.86からはこの変換が直接できるようになります。
use std::error::Error; use std::fmt::Display; fn hoge(disp: &dyn Display) { disp.to_string(); } fn fuga(err: &dyn Error) { // Displayトレイト→ToStringトレイトを経由したメソッド呼び出しはできる err.to_string(); // Rust 1.85まではエラー hoge(err); // Rust 1.85までも独自トレイトを挟むことで回避はできた hoge(err.as_display()); } // アップキャスト用回避策 trait MyError: Error { fn as_display(&self) -> &dyn Display; } impl<T: Error> MyError for T { fn as_display(&self) -> &dyn Display { self } }
スライスHashMapから複数の可変参照を得られるようになった
スライスとHashMapにget_disjoint_mutメソッドが追加され、複数の値への可変参照を得られるようになりました。
スライスでは添字の配列を渡すことで同要素数の配列に可変参照が格納され結果が返ってきます。
ただし添字が範囲外の場合、または入力配列に重複がある場合はErrが返ります。
HashMapではキーの配列を渡すことで同要素数の配列に可変参照がSomeに包まれつつ結果が返ってきます。
スライス版とは異なり、キーが存在しない場合は要素にNoneが入り、キーが被っている場合はパニックするという仕様です。
fn main() { let slice = &mut [100, 200, 300]; // Ok(100, 300, 200]) println!("{:?}", slice.get_disjoint_mut([0, 2, 1])); // Err(OverlappingIndices) println!("{:?}", slice.get_disjoint_mut([0, 0])); // Err(IndexOutOfBounds) println!("{:?}", slice.get_disjoint_mut([3])); let mut map: std::collections::HashMap<usize, usize> = [(0, 100), (1, 200), (2, 300)].into(); // [Some(100), Some(300), Some(200), None] println!("{:?}", map.get_disjoint_mut([&0, &2, &1, &3])); // panic: duplicate keys found map.get_disjoint_mut([&0, &0]); }
rustdocのフォントをsans-serifに変えられるようになった
これまで、rustdocのフォントはserif固定でした。 これは日本語では明朝体で表示されるため、ドキュメントに日本語を書いている場合は違和感を覚える方も多かったのではないでしょうか。
Rust 1.86からは設定から「Use sans serif fonts」を有効にすることでsans-serifに切り替えることができるようになり、 他のサイトと同様ゴシック体など見慣れたフォントで表示できるようになります。
安定化されたAPIのドキュメント
安定化されたAPIのドキュメントを独自に訳して紹介します。リストだけ見たい方は安定化されたAPIをご覧ください。
{float}::next_down
impl f64 { #[inline] #[doc(alias = "nextDown")] #[stable(feature = "float_next_up_down", since = "1.86.0")] #[rustc_const_stable(feature = "float_next_up_down", since = "1.86.0")] pub const fn next_down(self) -> Self { /* 実装は省略 */ } }
selfより小さい最大の数を返す。
TINYを最小の表現可能な正のf64とする時、次を満たす。
self.is_nan()の場合、selfを返すselfがINFINITYの場合、MAXを返すselfがTINYの場合、0.0を返すselfが-0.0または+0.0の場合、-TINYを返すselfがMINまたはNEG_INFINITYの場合、NEG_INFINITYを返す- それ以外の場合、
selfより小さい一意の最大値を返す
恒等式x.next_down() == -(-x).next_up()は非NaNのあらゆるxに対して成り立つ。
xが有限の場合、x == x.next_down().next_up()も成り立つ。
let x = 1.0f64; // 値を[0, 1)に収める let clamped = x.clamp(0.0, 1.0f64.next_down()); assert!(clamped < 1.0); assert_eq!(clamped.next_up(), 1.0);
この操作はIEEE-754のnextDownに対応する。
{float}::next_up
impl f64 { #[inline] #[doc(alias = "nextUp")] #[stable(feature = "float_next_up_down", since = "1.86.0")] #[rustc_const_stable(feature = "float_next_up_down", since = "1.86.0")] pub const fn next_up(self) -> Self { /* 実装は省略 */ } }
selfより大きい最小の数を返す。
TINYを最小の表現可能な正のf64とする時、次を満たす。
self.is_nan()の場合、selfを返すselfがNEG_INFINITYの場合、MINを返すselfが-TINYの場合、0.0を返すselfが-0.0または+0.0の場合、TINYを返すselfがMAXまたはINFINITYの場合、INFINITYを返す- それ以外の場合、
selfより大きい一意の最小値を返す
恒等式x.next_up() == -(-x).next_down()は非NaNのあらゆるxに対して成り立つ。
xが有限の場合、x == x.next_up().next_down()も成り立つ。
// f64::EPSILONは1.0と次の数との差 assert_eq!(1.0f64.next_up(), 1.0 + f64::EPSILON); // しかしほとんどの数ではそうではない assert!(0.1f64.next_up() < 0.1 + f64::EPSILON); assert_eq!(9007199254740992f64.next_up(), 9007199254740994.0);
この操作はIEEE-754のnextUpに対応する。
<[_]>::get_disjoint_mut
impl<T> [T] { #[stable(feature = "get_many_mut", since = "1.86.0")] #[inline] pub fn get_disjoint_mut<I, const N: usize>( &mut self, indices: [I; N], ) -> Result<[&mut I::Output; N], GetDisjointMutError> where I: GetDisjointMutIndex + SliceIndex<Self>, { /* 実装は省略 */ } }
複数の添字に対応する可変参照を返す。
添字はusizeやRange、またはRangeInclusiveのいずれかである。
なお、このメソッドは配列を受け取るので、すべての添字は同じ型でなければならない。
usizeの配列を渡した場合はこのメソッドは単一要素への可変参照の配列を返し、
Rangeの配列を渡した場合は可変参照スライスの配列を返す。
添字が範囲外の場合、または添字に重複がある場合はエラーを返す。 空のRangeは他のRangeの先頭または末尾にある場合は重複とはみなされないが、中間にある場合は重複とみなされる。
このメソッドは添字の重複検査をO(n2)で行うので、大量の添字を渡すときは注意すること。
サンプル
let v = &mut [1, 2, 3]; if let Ok([a, b]) = v.get_disjoint_mut([0, 2]) { *a = 413; *b = 612; } assert_eq!(v, &[413, 2, 612]); if let Ok([a, b]) = v.get_disjoint_mut([0..1, 1..3]) { a[0] = 8; b[0] = 88; b[1] = 888; } assert_eq!(v, &[8, 88, 888]); if let Ok([a, b]) = v.get_disjoint_mut([1..=2, 0..=0]) { a[0] = 11; a[1] = 111; b[0] = 1; } assert_eq!(v, &[1, 11, 111]);
<[_]>::get_disjoint_unchecked_mut
impl<T> [T] { #[stable(feature = "get_many_mut", since = "1.86.0")] #[inline] pub unsafe fn get_disjoint_unchecked_mut<I, const N: usize>( &mut self, indices: [I; N], ) -> [&mut I::Output; N] where I: GetDisjointMutIndex + SliceIndex<Self>, { /* 実装は省略 */ } }
検査なしで複数の添字に対応する可変参照を返す。
添字はusizeやRange、またはRangeInclusiveのいずれかである。
なお、このメソッドは配列を受け取るので、すべての添字は同じ型でなければならない。
usizeの配列を渡した場合はこのメソッドは単一要素への可変参照の配列を返し、
Rangeの配列を渡した場合は可変参照スライスの配列を返す。
安全な代替手段はget_disjoint_mutを参照。
安全性
重複した添字や範囲外の添字を指定してこのメソッドを呼び出すことは、 例え戻り値の参照を使わない場合でも未定義動作である。
サンプル
let x = &mut [1, 2, 4]; unsafe { let [a, b] = x.get_disjoint_unchecked_mut([0, 2]); *a *= 10; *b *= 100; } assert_eq!(x, &[10, 2, 400]); unsafe { let [a, b] = x.get_disjoint_unchecked_mut([0..1, 1..3]); a[0] = 8; b[0] = 88; b[1] = 888; } assert_eq!(x, &[8, 88, 888]); unsafe { let [a, b] = x.get_disjoint_unchecked_mut([1..=2, 0..=0]); a[0] = 11; a[1] = 111; b[0] = 1; } assert_eq!(x, &[1, 11, 111]);
slice::GetDisjointMutError
#[stable(feature = "get_many_mut", since = "1.86.0")] #[derive(Debug, Clone, PartialEq, Eq)] pub enum GetDisjointMutError { IndexOutOfBounds, OverlappingIndices, }
get_disjoint_mutが返すエラー型。
次のうちいずれかのエラーを示す。
- 添字が範囲外
- 重複した添字(Rangeを指定した場合は異なっていようが重複した添字)が配列中にある
サンプル
use std::slice::GetDisjointMutError; let v = &mut [1, 2, 3]; assert_eq!(v.get_disjoint_mut([0, 999]), Err(GetDisjointMutError::IndexOutOfBounds)); assert_eq!(v.get_disjoint_mut([1, 1]), Err(GetDisjointMutError::OverlappingIndices));
バリアント
IndexOutOfBounds
指定された添字がスライスの範囲外だった。
OverlappingIndices
指定された2つの添字が重複していた。
HashMap::get_disjoint_mut
impl<K, V, S> HashMap<K, V, S> where K: Eq + Hash, S: BuildHasher, { #[inline] #[doc(alias = "get_many_mut")] #[stable(feature = "map_many_mut", since = "1.86.0")] pub fn get_disjoint_mut<Q: ?Sized, const N: usize>( &mut self, ks: [&Q; N], ) -> [Option<&'_ mut V>; N] where K: Borrow<Q>, Q: Hash + Eq, { /* 実装は省略 */ } }
連想配列からN個の値への可変参照の取得を試みる。
各問い合わせの結果をN要素の配列で返す。健全性のため、同じ値への可変参照は最大1つまで返される。
キーが見つからない場合はNoneが使用される。
パニック
キーが重複している場合にパニックする。
サンプル
use std::collections::HashMap; let mut eras = HashMap::new(); eras.insert("明治".to_string(), 1868); eras.insert("大正".to_string(), 1912); eras.insert("昭和".to_string(), 1925); eras.insert("平成".to_string(), 1989); // 大正と明治を取得 let [Some(a), Some(b)] = eras.get_disjoint_mut([ "大正", "明治", ]) else { panic!() }; // 大正と平成の値を検証 let got = eras.get_disjoint_mut([ "大正", "平成", ]); assert_eq!( got, [ Some(&mut 1912), Some(&mut 1989), ], ); // 見つからないキーはNone let got = eras.get_disjoint_mut([ "大正", "令和", ]); assert_eq!( got, [ Some(&mut 1912), None ] );
use std::collections::HashMap; let mut eras = HashMap::new(); eras.insert("大正".to_string(), 1912); // 重複したキーはパニックする let got = eras.get_disjoint_mut([ "大正", "大正", ]);
HashMap::get_disjoint_unchecked_mut
impl<K, V, S> HashMap<K, V, S> where K: Eq + Hash, S: BuildHasher, { #[inline] #[doc(alias = "get_many_unchecked_mut")] #[stable(feature = "map_many_mut", since = "1.86.0")] pub unsafe fn get_disjoint_unchecked_mut<Q: ?Sized, const N: usize>( &mut self, ks: [&Q; N], ) -> [Option<&'_ mut V>; N] where K: Borrow<Q>, Q: Hash + Eq, { /* 実装は省略 */ } }
各値が一意であることを検証せずに、連想配列からN個の値への可変参照の取得を試みる。
各問い合わせの結果をN要素の配列で返す。キーが見つからない場合はNoneが使用される。
安全な代替手段はget_disjoint_mutを参照。
安全性
重複したキーを指定してこのメソッドを呼び出すことは、例え戻り値の参照を使わない場合でも未定義動作である。
サンプル
use std::collections::HashMap; let mut eras = HashMap::new(); eras.insert("明治".to_string(), 1868); eras.insert("大正".to_string(), 1912); eras.insert("昭和".to_string(), 1925); eras.insert("平成".to_string(), 1989); // SAFETY: キーは重複しない let [Some(a), Some(b)] = (unsafe { eras.get_disjoint_unchecked_mut([ "大正", "明治", ]) }) else { panic!() }; // SAFETY: キーは重複しない let got = unsafe { eras.get_disjoint_unchecked_mut([ "大正", "平成", ]) }; assert_eq!( got, [ Some(&mut 1912), Some(&mut 1989), ], ); // SAFETY: キーは重複しない let got = unsafe { eras.get_disjoint_unchecked_mut([ "大正", "令和", ]) }; // 見つからないキーはNone assert_eq!(got, [Some(&mut 1912), None]);
NonZero::count_ones
impl NonZero<u8> { #[stable(feature = "non_zero_count_ones", since = "1.86.0")] #[rustc_const_stable(feature = "non_zero_count_ones", since = "1.86.0")] #[doc(alias = "popcount")] #[doc(alias = "popcnt")] #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline(always)] pub const fn count_ones(self) -> NonZero<u32> { /* 実装は省略 */ } }
selfの2進数表現に含まれる1の数を返す。
サンプル
基本的な使い方。
let a = NonZero::<u8>::new(0b100_0000)?; let b = NonZero::<u8>::new(0b100_0011)?; assert_eq!(a.count_ones(), NonZero::new(1)?); assert_eq!(b.count_ones(), NonZero::new(3)?);
use std::num::NonZero;
fn main() { test().unwrap(); }
fn test() -> Option<()> {
let a = NonZero::<u8>::new(0b100_0000)?;
let b = NonZero::<u8>::new(0b100_0011)?;
assert_eq!(a.count_ones(), NonZero::new(1)?);
assert_eq!(b.count_ones(), NonZero::new(3)?);
Some(())
}
Vec::pop_if
impl<T, A: Allocator> Vec<T, A> { #[stable(feature = "vec_pop_if", since = "1.86.0")] pub fn pop_if(&mut self, predicate: impl FnOnce(&mut T) -> bool) -> Option<T> { /* 実装は省略 */ } }
述語がtrueを返す場合はベクタから最後の要素を削除して返し、
述語がfalseを返す場合やベクタが空の場合(この時述語は呼び出されない)はNoneを返す。
サンプル
let mut vec = vec![1, 2, 3, 4]; let pred = |x: &mut i32| *x % 2 == 0; assert_eq!(vec.pop_if(pred), Some(4)); assert_eq!(vec, [1, 2, 3]); assert_eq!(vec.pop_if(pred), None);
sync::Once::wait
impl Once { #[stable(feature = "once_wait", since = "1.86.0")] pub fn wait(&self) { /* 実装は省略 */ } }
初期化が完了するまで現在のスレッドをブロックする。
サンプル
use std::sync::Once; use std::thread; static READY: Once = Once::new(); let thread = thread::spawn(|| { READY.wait(); println!("すべて準備完了"); }); READY.call_once(|| println!("セットアップ実行中"));
パニック
初期化用クロージャがパニックを起こしてこのOnceが中毒状態になった場合、
このメソッドもパニックを起こす。この挙動が望ましくない場合はwait_forceを使用すること。
sync::Once::wait_force
impl Once { #[stable(feature = "once_wait", since = "1.86.0")] pub fn wait_force(&self) { /* 実装は省略 */ } }
中毒状態を無視しつつ、初期化が完了するまで現在のスレッドをブロックする。
sync::OnceLock::wait
impl<T> OnceLock<T> { #[inline] #[stable(feature = "once_wait", since = "1.86.0")] pub fn wait(&self) -> &T { /* 実装は省略 */ } }
区画が初期化されるまで現在のスレッドをブロックする。
サンプル
別スレッドが計算を終えるのを待つ。
use std::thread; use std::sync::OnceLock; let value = OnceLock::new(); thread::scope(|s| { s.spawn(|| value.set(1 + 1)); let result = value.wait(); assert_eq!(result, &2); })
変更点リスト
言語
- トレイトオブジェクトの親トレイトへのアップキャストを安定化
- 安全関数に
#[target_feature]属性を付けられるようにする。 - リント
missing_abiが既定警告になった - 二重否定についての警告が追加され、他の言語における前置デクリメント演算子(
--x)を意図した書き方を捕捉できるようになった。以前はclippyのリントclippy::double_negだったが、現在はRustで直接利用可能になった - 定数評価において、アライメントに基づきより多くのポインタが確実に非NULLであると判定されるようになった
- 無効な項目に適用された空の
repr()が正しく却下されるようになった - 内部属性
#![test]と#![rustfmt::skip]が想定より広い範囲で受け入れられることがなくなった
コンパイラ
- 生ポインタへのアクセス時に非NULLであることをデバッグ時に検証
- Cargoの標準設定と一致するよう、
-Oの意味が-C opt-level=2から-C opt-level=3に変更された - 一部マクロ環境下での
overflowing_literalsの出力を修正
プラットフォーム対応
i686-unknown-redoxターゲットをi586-unknown-redoxで置き換えi686-unknown-hurd-gnuの基準CPUをPentium 4に引き上げ- 新たなティア3ターゲット
{aarch64-unknown,x86_64-pc}-nto-qnx710_iosock。io-socketネットワークソケット入りのNeutrino QNX 7.1への対応{aarch64-unknown,x86_64-pc}-nto-qnx800。 Neutrino QNX 8.0(no_stdのみ)への対応{x86_64,i686}-win7-windows-gnu。 Windows 7との後方互換性用。Windows MSVCで対となる{x86_64,i686}-win7-windows-msvcは既にティア3ターゲットとして存在するamdgcn-amd-amdhsax86_64-pc-cygwin{mips,mipsel}-mti-none-elf。 初期のベアメタル対応m68k-unknown-none-elfarmv7a-nuttx-{eabi,eabihf}・aarch64-unknown-nuttx・thumbv7a-nuttx-{eabi,eabihf}.
Rustのティア付けされたプラットフォーム対応の詳細はPlatform Supportのページ(※訳注:英語)を参照
ライブラリ
CStr::from_bytes_with_nul(bytes: &[u8]) -> Result<&Self, FromBytesWithNulError>におけるFromBytesWithNulError型が不透明(opaque)な構造体から列挙型に変更され、変換に失敗した理由を確認出来るようになったRustcDecodableとRustcEncodableを削除- libtestの
--logfileオプションを非推奨化 - 最近のWindowsのバージョンにおいて、
std::fs::remove_fileが読み取り専用のファイルを削除するようになった
安定化されたAPI
{float}::next_down{float}::next_up<[_]>::get_disjoint_mut<[_]>::get_disjoint_unchecked_mutslice::GetDisjointMutErrorHashMap::get_disjoint_mutHashMap::get_disjoint_unchecked_mutNonZero::count_onesVec::pop_ifsync::Once::waitsync::Once::wait_forcesync::OnceLock::wait
以下のAPIが定数文脈で使えるようになった。
hint::black_boxio::Cursor::get_mutio::Cursor::set_positionstr::is_char_boundarystr::split_atstr::split_at_checkedstr::split_at_mutstr::split_at_mut_checked
Cargo
- 設定統合時、プログラムのパスと引数を指定するキーは結合ではなく置換
--packageと--workspaceが両方指定されたが、要求されたパッケージが見つからない場合はエラー。以前はこれは黙って無視していたが、パッケージが見つからない場合は報告すべきであることからバグと判断された- シェルの履歴へのトークン漏洩を防止するため、
cargo loginのトークン引数を非推奨化 SourceIDの比較処理を簡素化。代替レジストリでの正規化されたURLの比較動作が変わる可能性がある
Rustdoc
互換性メモ
- 将来の互換性に関する警告
wasm_c_abiが致命的なエラーとなった。wasm-bindgenを使用している場合は0.2.89以上へアップグレードする必要がある。さもなくばコンパイルは失敗する - 長期間非推奨だった、何もしない属性
#![no_start]と#![crate_id]を削除 - 将来の互換性に関するリント
cenum_impl_drop_castが致命的なエラーとなった。これにより、Dropを実装しているフィールドなし列挙型の整数へのキャストがエラーとなった - 32ビットx86 hard-floatターゲットの"i686"にはSSE2が必須となった。これを無効化すると警告され、将来的には致命的なエラーが発生する。 SSE2以前の32ビットx86向けにコンパイルするには"i586"ターゲットを使用する必要がある
内部の変更
これらの変更がユーザーに直接利益をもたらすわけではないものの、コンパイラ及び周辺ツール内部では重要なパフォーマンス改善をもたらす。
- AArch64 LinuxのrustcをThinLTOとPGOでビルド。 Linux上のARM 64ビットコンパイラ(AArch64)が、Linuxのx86-64コンパイラ向けに行っているのと同様に、ThinLTOとPGOで最適化されるようになった。これにより最大30%の速度向上が期待できる
関連リンク
さいごに
次のリリースのRust 1.87は5/16(金)にリリースされる予定です。
Rust 1.87ではenv::home_dir関数が再度使えるようになるようです。