本日11/29(金)にリリースされたRust 1.83の変更点を詳しく紹介します。 もしこの記事が参考になれば記事末尾から活動を支援頂けると嬉しいです。
- ピックアップ
- 安定化されたAPIのドキュメント
- char::MIN
- DebugMap::finish_non_exhaustive
- DebugSet::finish_non_exhaustive
- DebugList::finish_non_exhaustive
- DebugTuple::finish_non_exhaustive
- ControlFlow::break_value
- ControlFlow::map_break
- ControlFlow::continue_value
- ControlFlow::map_continue
- Option::get_or_insert_default
- Waker::new
- Waker::data
- Waker::vtable
- Entry::insert_entry
- VacantEntry::insert_entry
- BufRead::skip_until
- 変更点リスト
- 関連リンク
- さいごに
- ライセンス表記
ピックアップ
個人的に注目する変更点を「ピックアップ」としてまとめました。 全ての変更点を網羅したリストは変更点リストをご覧ください。
静的変数への参照を定数として得られるようになった
これまでstatic変数への参照を得ることはできず、定数文脈においてconst fnの引数に参照がある場合にstatic変数を渡すことができませんでした。
Rust 1.83からは定数文脈であってもstatic変数への参照を得ることができるようになり、非定数文脈とのコード統一ができるようになりました。
struct S(pub u32); impl S { const fn add(&self, x: u32) -> S { S(self.0 + x) } } static X: S = S(32); const Y: S = X.add(10); fn main() { // どちらも42 println!("{}", Y.0); println!("{}", X.add(10).0); }
安定化されたAPIのドキュメント
安定化されたAPIのドキュメントを独自に訳して紹介します。リストだけ見たい方は安定化されたAPIをご覧ください。
char::MIN
impl char { #[stable(feature = "char_min", since = "1.83.0")] pub const MIN: char = '\0'; }
charが持てる最小の有効なコードポイント、即ち'\0'。
数値型とは異なりcharの中間には欠落があるため、使用可能なcharの範囲は想像以上に少ないことに注意。
charの範囲は自動的にこの欠落を飛び越す。
let dist = u32::from(char::MAX) - u32::from(char::MIN); let size = (char::MIN..=char::MAX).count() as u32; assert!(size < dist);
この欠落があるとは言え、MINとMAXの値はあらゆるchar値の境界として使うことができる。
サンプル
let c: char = something_which_returns_char(); assert!(char::MIN <= c); let value_at_min = u32::from(char::MIN); assert_eq!(char::from_u32(value_at_min), Some('\0'));
fn something_which_returns_char() -> char { 'a' }
fn main() {
let c: char = something_which_returns_char();
assert!(char::MIN <= c);
let value_at_min = u32::from(char::MIN);
assert_eq!(char::from_u32(value_at_min), Some('\0'));
}
DebugMap::finish_non_exhaustive
impl<'a, 'b: 'a> DebugMap<'a, 'b> { #[stable(feature = "debug_more_non_exhaustive", since = "1.83.0")] pub fn finish_non_exhaustive(&mut self) -> fmt::Result { /* 実装は省略 */ } }
連想配列を非網羅と設定し、デバッグ表現に含まれない他のエントリがあることを読み手に示す。
サンプル
use std::fmt; struct Foo(Vec<(String, i32)>); impl fmt::Debug for Foo { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { // 最大2つの要素を表示し残りは省略 let mut f = fmt.debug_map(); let mut f = f.entries(self.0.iter().take(2).map(|&(ref k, ref v)| (k, v))); if self.0.len() > 2 { f.finish_non_exhaustive() } else { f.finish() } } } assert_eq!( format!("{:?}", Foo(vec![ ("A".to_string(), 10), ("B".to_string(), 11), ("C".to_string(), 12), ])), r#"{"A": 10, "B": 11, ..}"#, );
DebugSet::finish_non_exhaustive
impl<'a, 'b: 'a> DebugSet<'a, 'b> { #[stable(feature = "debug_more_non_exhaustive", since = "1.83.0")] pub fn finish_non_exhaustive(&mut self) -> fmt::Result { /* 実装は省略 */ } }
集合を非網羅と設定し、デバッグ表現に含まれない他の要素があることを読み手に示す。
サンプル
use std::fmt; struct Foo(Vec<i32>); impl fmt::Debug for Foo { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { // 最大2つの要素を表示し残りは省略 let mut f = fmt.debug_set(); let mut f = f.entries(self.0.iter().take(2)); if self.0.len() > 2 { f.finish_non_exhaustive() } else { f.finish() } } } assert_eq!( format!("{:?}", Foo(vec![1, 2, 3, 4])), "{1, 2, ..}", );
DebugList::finish_non_exhaustive
impl<'a, 'b: 'a> DebugList<'a, 'b> { #[stable(feature = "debug_more_non_exhaustive", since = "1.83.0")] pub fn finish_non_exhaustive(&mut self) -> fmt::Result { /* 実装は省略 */ } }
リストを非網羅と設定し、デバッグ表現に含まれない他の要素があることを読み手に示す。
サンプル
use std::fmt; struct Foo(Vec<i32>); impl fmt::Debug for Foo { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { // 最大2つの要素を表示し残りは省略 let mut f = fmt.debug_list(); let mut f = f.entries(self.0.iter().take(2)); if self.0.len() > 2 { f.finish_non_exhaustive() } else { f.finish() } } } assert_eq!( format!("{:?}", Foo(vec![1, 2, 3, 4])), "[1, 2, ..]", );
DebugTuple::finish_non_exhaustive
impl<'a, 'b: 'a> DebugTuple<'a, 'b> { #[stable(feature = "debug_more_non_exhaustive", since = "1.83.0")] pub fn finish_non_exhaustive(&mut self) -> fmt::Result { /* 実装は省略 */ } }
タプルを非網羅と設定し、デバッグ表現に含まれない他の要素があることを読み手に示す。
サンプル
use std::fmt; struct Hoge(i32, String); impl fmt::Debug for Hoge { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_tuple("Hoge") .field(&self.0) .finish_non_exhaustive() // 他にもフィールドがあることを示す } } assert_eq!( format!("{:?}", Hoge(10, "ひ・み・つ".to_owned())), "Hoge(10, ..)", );
ControlFlow::break_value
impl<B, C> ControlFlow<B, C> { #[inline] #[stable(feature = "control_flow_enum", since = "1.83.0")] pub fn break_value(self) -> Option<B> { /* 実装は省略 */ } }
ControlFlowをBreakだった場合はSomeに、そうでない場合はNoneに変換する。
サンプル
use std::ops::ControlFlow; assert_eq!(ControlFlow::<i32, String>::Break(3).break_value(), Some(3)); assert_eq!(ControlFlow::<String, i32>::Continue(3).break_value(), None);
ControlFlow::map_break
impl<B, C> ControlFlow<B, C> { #[inline] #[stable(feature = "control_flow_enum", since = "1.83.0")] pub fn map_break<T>(self, f: impl FnOnce(B) -> T) -> ControlFlow<T, C> { /* 実装は省略 */ } }
Breakの値が存在する場合は関数を適用し、ControlFlow<B, C>をControlFlow<T, C>に変換する。
ControlFlow::continue_value
impl<B, C> ControlFlow<B, C> { #[inline] #[stable(feature = "control_flow_enum", since = "1.83.0")] pub fn continue_value(self) -> Option<C> { /* 実装は省略 */ } }
ControlFlowをContinueだった場合はSomeに、そうでない場合はNoneに変換する。
サンプル
use std::ops::ControlFlow; assert_eq!(ControlFlow::<i32, String>::Break(3).continue_value(), None); assert_eq!(ControlFlow::<String, i32>::Continue(3).continue_value(), Some(3));
ControlFlow::map_continue
impl<B, C> ControlFlow<B, C> { #[inline] #[stable(feature = "control_flow_enum", since = "1.83.0")] pub fn map_continue<T>(self, f: impl FnOnce(C) -> T) -> ControlFlow<B, T> { /* 実装は省略 */ } }
Continueの値が存在する場合は関数を適用し、ControlFlow<B, C>をControlFlow<B, T>に変換する。
Option::get_or_insert_default
impl<T> Option<T> { #[inline] #[stable(feature = "option_get_or_insert_default", since = "1.83.0")] pub fn get_or_insert_default(&mut self) -> &mut T where T: Default, { /* 実装は省略 */ } }
Noneだった場合は既定値を挿入し、内包する値への可変参照を返す。
サンプル
let mut x = None; { let y: &mut u32 = x.get_or_insert_default(); assert_eq!(y, &0); *y = 7; } assert_eq!(x, Some(7));
Waker::new
impl Waker { #[inline] #[must_use] #[stable(feature = "waker_getters", since = "1.83.0")] #[rustc_const_stable(feature = "waker_getters", since = "1.83.0")] pub const unsafe fn new(data: *const (), vtable: &'static RawWakerVTable) -> Self { /* 実装は省略 */ } }
指定されたdataポインタとvtableから新しいWakerを作成する。
dataポインタはexecutorに必要な任意のデータを格納するために使用できる。
これはタスクに関連付けられたArcへの型消去されたポインタなどが考えられる。
このポインタの値は、vtableに属するすべての関数に、最初の引数として渡される。
ここで、dataポインタはArcなどスレッドセーフな型を指す必要があることが重要。
vtableはWakerの動作をカスタマイズする。
安全性
RawWakerVTableの文書で定義されている規約が守られていない場合、返されたWakerの振る舞いは未定義である。
(非安全なコードを避けたい場合、ヒープへの確保を代償にWakeトレイトを実装することもできる)
Waker::data
impl Waker { #[inline] #[must_use] #[stable(feature = "waker_getters", since = "1.83.0")] pub fn data(&self) -> *const () { /* 実装は省略 */ } }
Wakerの生成時に渡されたdataポインタを取得する。
Waker::vtable
impl Waker { #[inline] #[must_use] #[stable(feature = "waker_getters", since = "1.83.0")] pub fn vtable(&self) -> &'static RawWakerVTable { /* 実装は省略 */ } }
Wakerの生成時に渡されたvtableポインタを取得する。
Entry::insert_entry
impl<'a, K, V> Entry<'a, K, V> { #[inline] #[stable(feature = "entry_insert", since = "1.83.0")] pub fn insert_entry(self, value: V) -> OccupiedEntry<'a, K, V> { /* 実装は省略 */ } }
エントリの値を設定しOccupiedEntryを返す。
サンプル
use std::collections::HashMap; let mut map: HashMap<&str, String> = HashMap::new(); let entry = map.entry("poneyland").insert_entry("hoho".to_string()); assert_eq!(entry.key(), &"poneyland");
VacantEntry::insert_entry
impl<'a, K: 'a, V: 'a> VacantEntry<'a, K, V> { #[inline] #[stable(feature = "entry_insert", since = "1.83.0")] pub fn insert_entry(self, value: V) -> OccupiedEntry<'a, K, V> { /* 実装は省略 */ } }
VacantEntryのキーに値を設定し、OccupiedEntryを返す。
サンプル
use std::collections::HashMap; use std::collections::hash_map::Entry; let mut map: HashMap<&str, u32> = HashMap::new(); if let Entry::Vacant(o) = map.entry("poneyland") { o.insert_entry(37); } assert_eq!(map["poneyland"], 37);
BufRead::skip_until
pub trait BufRead: Read { #[stable(feature = "bufread_skip_until", since = "1.83.0")] fn skip_until(&mut self, byte: u8) -> Result<usize> { /* 実装は省略 */ } }
区切り文字byteまたはEOFに到達するまで、全てのバイト列をスキップする。
この関数は、区切り文字またはEOFが見つかるまで基底ストリームからバイト列を読み取る(また破棄する)。
関数が成功した場合、区切りバイトを含む読み取ったバイト数を返す。
バイナリ形式のファイルからNUL終端文字列などのデータをバッファリングせず効率的に読み飛ばすのに有用。
この関数はブロックするため慎重に使用する必要がある。 攻撃者が区切り文字やEOFを送信せずに連続してバイト列を送信する可能性がある。
エラー
この関数はfill_bufが返すエラーのうちErrorKind::Interruptedの全インスタンスを無視し、
それ以外のエラーはそのまま返す。
I/Oエラーが発生した場合、これまでに読み取った全バイト列がbufに含まれ、その長さが適切に調整される。
サンプル
std::io::CursorはBufReadを実装する型である。
次の例ではCursorを使ってバイナリ文字列からFerrisに関するNUL終端の情報を読み取るが、小ネタは読み飛ばしている。
use std::io::{self, BufRead}; let mut cursor = io::Cursor::new(b"Ferris\0Likes long walks on the beach\0Crustacean\0"); // 名前を読み取る let mut name = Vec::new(); let num_bytes = cursor.read_until(b'\0', &mut name) .expect("cursorからの読み取りは失敗しない"); assert_eq!(num_bytes, 7); assert_eq!(name, b"Ferris\0"); // 小ネタは読み飛ばす let num_bytes = cursor.skip_until(b'\0') .expect("cursorからの読み取りは失敗しない"); assert_eq!(num_bytes, 30); // 動物の分類を読み取る let mut animal = Vec::new(); let num_bytes = cursor.read_until(b'\0', &mut animal) .expect("cursorからの読み取りは失敗しない"); assert_eq!(num_bytes, 11); assert_eq!(animal, b"Crustacean\0");
変更点リスト
言語
- 定数文脈での
&mut・*mut・&Cellおよび*const Cellを安定化 const初期化子での静的変数への参照の作成を許容- 生のライフタイムとラベル(
'r#ident)を実装 - 原子変数と非原子変数の読み取りが競合した場合の挙動を定義
- 非網羅的な構造体が空でも良くなった
!型を指す場所からの暗黙的な型変換を禁止const extern関数が他の呼び出し規約でも定義できるようになったexpr_2021マクロフラグメント指定子を全エディションで安定化- リント
non_local_definitionsがより小さいコードでも発火し、既定で警告するようになった
コンパイラ
- 不良な
-Csoft-floatフラグを非推奨化 - 新たなティア3ターゲットを多数追加
Rustのティア付けされたプラットフォーム対応の詳細はPlatform Supportのページ(※訳注:英語)を参照
ライブラリ
ExitCodeがPartialEqを実装するようになったcatch_unwindが未定義動作なしで外部例外を処理できることを文書化し、外部例外についての挙動は不定であることを明記HashMap・HashSetのイテレータで実装していないものがDefaultを実装するようになった- Unicodeを16.0.0にバージョンアップ
ptr::add・subの文書を、offsetとの同等性を主張しないよう変更
安定化されたAPI
BufRead::skip_untilControlFlow::break_valueControlFlow::continue_valueControlFlow::map_breakControlFlow::map_continueDebugList::finish_non_exhaustiveDebugMap::finish_non_exhaustiveDebugSet::finish_non_exhaustiveDebugTuple::finish_non_exhaustiveErrorKind::ArgumentListTooLongErrorKind::DeadlockErrorKind::DirectoryNotEmptyErrorKind::ExecutableFileBusyErrorKind::FileTooLargeErrorKind::HostUnreachableErrorKind::IsADirectoryErrorKind::NetworkDownErrorKind::NetworkUnreachableErrorKind::NotADirectoryErrorKind::NotSeekableErrorKind::ReadOnlyFilesystemErrorKind::ResourceBusyErrorKind::StaleNetworkFileHandleErrorKind::StorageFullErrorKind::TooManyLinksOption::get_or_insert_defaultWaker::dataWaker::newWaker::vtablechar::MINhash_map::Entry::insert_entryhash_map::VacantEntry::insert_entry
以下のAPIが定数文脈で使えるようになった。
Cell::into_innerDuration::as_secs_f32Duration::as_secs_f64Duration::div_duration_f32Duration::div_duration_f64MaybeUninit::as_mut_ptrNonNull::as_mutNonNull::copy_fromNonNull::copy_from_nonoverlappingNonNull::copy_toNonNull::copy_to_nonoverlappingNonNull::slice_from_raw_partsNonNull::writeNonNull::write_bytesNonNull::write_unalignedOnceCell::into_innerOption::as_mutOption::expectOption::replaceOption::takeOption::unwrapOption::unwrap_uncheckedOption::<&_>::copiedOption::<&mut _>::copiedOption::<Option<_>>::flattenOption::<Result<_, _>>::transposeRefCell::into_innerResult::as_mutResult::<&_, _>::copiedResult::<&mut _, _>::copiedResult::<Option<_>, _>::transposeUnsafeCell::get_mutUnsafeCell::into_innerarray::from_mutchar::encode_utf8{float}::classify{float}::is_finite{float}::is_infinite{float}::is_nan{float}::is_normal{float}::is_sign_negative{float}::is_sign_positive{float}::is_subnormal{float}::from_bits{float}::from_be_bytes{float}::from_le_bytes{float}::from_ne_bytes{float}::to_bits{float}::to_be_bytes{float}::to_le_bytes{float}::to_ne_bytesmem::replaceptr::replaceptr::slice_from_raw_parts_mutptr::writeptr::write_unaligned<*const _>::copy_to<*const _>::copy_to_nonoverlapping<*mut _>::copy_from<*mut _>::copy_from_nonoverlapping<*mut _>::copy_to<*mut _>::copy_to_nonoverlapping<*mut _>::write<*mut _>::write_bytes<*mut _>::write_unalignedslice::from_mutslice::from_raw_parts_mut<[_]>::first_mut<[_]>::last_mut<[_]>::first_chunk_mut<[_]>::last_chunk_mut<[_]>::split_at_mut<[_]>::split_at_mut_checked<[_]>::split_at_mut_unchecked<[_]>::split_first_mut<[_]>::split_last_mut<[_]>::split_first_chunk_mut<[_]>::split_last_chunk_mutstr::as_bytes_mutstr::as_mut_ptrstr::from_utf8_unchecked_mut
Cargo
- 新しい
CARGO_MANIFEST_PATH環境変数を導入。CARGO_MANIFEST_DIRと同様だが、マニフェストファイルを直接指す - マニフェストに
package.autolibを追加し、[lib]の自動検出を無効化 - 各クレートのサポートレベルをCargoの憲章やクレートの文書で宣言
- 新しい意図的な成果物を「小さな」変更として宣言
Rustdoc
- サイドバー・ハンバーガーメニューの目次に、主アイテムの文書化コメントからの
# ヘッダーが含まれるようになった。これはサードパーティのブラウザ拡張であるrustdoc-search-enhancementsの機能に近い。
互換性メモ
- 非対応のABI文字列を使った関数ポインタについて警告
- 関数ポインタのキャストで元の型のシグネチャが整形されているか確認。 これは関数アイテムを関数ポインタにキャストする際に生じる不良動作の穴を部分的に埋めるもの
- 型依存パスの解決時に部分型化の代わりに等価性を使うように
- macOSでのリンク時にRustの既定デプロイ対象が正しく含まれるようになった。リンカのバグのため、正しいフレームワークを指定するために
MACOSX_DEPLOYMENT_TARGETを渡すか、#[link]属性を修正する必要があるかもしれない。詳細はhttps://github.com/rust-lang/rust/pull/129369を参照 - 非
struct・enum・unionアイテムに書かれたrepr(Rust)に対して正しくエラーを発生させるようになった。これまでは何の効果もなかった - 将来の非互換性リント
deprecated_cfg_attr_crate_type_nameがコンパイルエラーになった。これは#![cfg_attr]で#![crate_type]や#![crate_name]属性を使うことを拒否するもので、コンパイラにおいてcfgの展開後に使われるクレート種別やクレート名を変更できるようにするためのハックだった ユーザーはコマンドラインでrustc・cargo rustcを実行する際に#![cfg_attr(..., crate_type = "...")]や--crate-nameの代わりに--crate-typeを使うことができる。 これらの2つの属性は#![cfg_attr]外部での使用にも改善に対応している これまで診断機能やコード生成・バックトレースでのsysroot内のパスは次のように
/rustc/$hashで始まっていたthread 'main' panicked at 'hello world', map-panic.rs:2:50 stack backtrace: 0: std::panicking::begin_panic at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/std/src/panicking.rs:616:12 1: map_panic::main::{{closure}} at ./map-panic.rs:2:50 2: core::option::Option<T>::map at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/core/src/option.rs:929:29 3: map_panic::main at ./map-panic.rs:2:30 4: core::ops::function::FnOnce::call_once at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/core/src/ops/function.rs:248:5 note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.RFC 3127には次のようにある
この挙動を次のように変更したい。
rust-srcのソースファイルが見つかる場合、仮想のパスは破棄されローカルパスを埋め込む。ただし--remap-path-prefixが指定された場合、ローカルパスは通常の方法で再変換する。#129687でこの挙動を実装し、コンパイル時に
rust-srcが存在する場合、rustcは/rustc/$hashをローカルのrust-srcコンポーネントへの実パスにできるだけ置き換えるようになった。 これを校正するには、ユーザーは明示的に--remap-path-prefix=<rust-srcへのパス>=fooを指定するか、rust-srcコンポーネントがインストールされていない必要がある- 既定許可のリント
missing_docsはこれまで、rustc --test・cargo test経由で呼び出された際に自動的に無効化されていた。これにより#[expect(missing_docs)]が誤って満たされていなかった。この挙動は削除され#[expect(missing_docs)]があらゆる事例で満たされるようになったが、外部から到達可能な#[cfg(test)]アイテムや統合テストのクレート文書、および統合テスト内の外部から到達可能なアイテムに対して新しいmissing_docs診断が報告されるようになった armv8r-none-eabihfターゲットがArmv8-Rで必要な浮動小数点機能集合を使用するようになったdyn Traitの関連型において、親トレイトによって生じる制約のない高階ライフタイムを検出しなかった不良動作を修正- 最小の外部LLVMを18に引き上げ
aarch64-unknown-fuchsiaとx86_64-unknown-fuchsiaターゲットへの置き換えのため、それぞれの別名であるaarch64-fuchsiaとx86_64-fuchsiaを削除- RustのパニックであるABI級例外クラスが元来のエンディアンでエンコードされるようになり、16進ダンプで読みやすくなった
- MSVCターゲットがVisual Studio 2013に対応しなくなった
- sysrootの最上位
lib/ディレクトリにstdの動的ライブラリが含まれなくなった
関連リンク
さいごに
次のリリースのRust 1.84は2025/1/10(金)にリリースされる予定です。 Rust 1.84ではいい感じの新機能はなさそうです。