本日7/14(金)にリリースされたRust 1.71の変更点を詳しく紹介します。 もしこの記事が参考になれば記事末尾から活動を支援頂けると嬉しいです。
ピックアップ
個人的に注目する変更点を「ピックアップ」としてまとめました。 全ての変更点を網羅したリストは変更点リストをご覧ください。
タプルと配列を相互に変換できるようになった
From
・Into
トレイトによりタプルと配列を相互に変換できるようになりました。
ただし要素数は1~12に限られます。
fn main() { let tuple = (0, 1, 2, 3); // タプルから配列への変換 let array: [i32; 4] = tuple.into(); assert_eq!(array, [0, 1, 2, 3]); // 配列からタプルへの変換 let tuple: (i32, i32, i32, i32) = array.into(); assert_eq!(tuple, (0, 1, 2, 3)); }
ネストしたformat_args!
が平坦化されるようになった
Rust 1.70で入ったformat_args!
の展開に引き続き、Rust 1.71ではネストしたformat_args!
の呼び出しが平坦化されるようになりました。
前回と同じくこちらも出力コードが最適化され、高速化やバイナリサイズの縮小などが期待されます。
fn main() { // このようなネストしたformat_args!は展開され・・・ println!("Hello, {}!", format_args!("world")); // このように書くのとほぼ同じ意味となる println!("Hello, world!"); // Rust 1.70までは`None`、1.71以降は`Some("Hello, world!")` println!("{:?}", format_args!("Hello, {}!", format_args!("world")).as_str()); }
巻き戻し(パニック)ができるABIが使えるようになった
extern "C"
に代表される各種ABIでは境界を跨ぐ巻き戻しが行えず、現在のRustではextenrn "C"
関数内でパニックした場合の挙動は未定義です。
これはlibpngやlibjpegなどコールバック式でユーザー関数を指定するライブラリでは非常に不便な仕様でした。
Rust 1.71ではextern "C-unwind"
等によって巻き戻しのできるABIを指定できるようになりました。
今回使えるようになったABI文字列は以下の通りです。
"C-unwind"
"cdecl-unwind"
"stdcall-unwind"
"fastcall-unwind"
"vectorcall-unwind"
"thiscall-unwind"
"aapcs-unwind"
"win64-unwind"
"sysv64-unwind"
"system-unwind"
なお、将来的にはextern "C"
関数内でパニックした場合にabort
するよう挙動が変更される予定です。
ただし上記は既定の話であって、rustcに-Cpanic=abort
の指示(あるいはCargo.tomlのプロファイルにpanic = "abort"
の設定)がある場合、
パニック時はABIに関わらずabort
します。
最近のrust-analyzer
最近rust-analyzerに入った変更の中から、個人的に気になったものをピックアップしました。
メモリレイアウトの進数を変えられるようになった
2023-06-05(v0.3.1541)での変更です。
構造体等のかざし(hover)ヒントに表示されるメモリレイアウトのオフセットや大きさを10進数・16進数・両方の中から選べるようになりました。 これまでは16進数で表示されていたので手計算が面倒でした。
これは設定rust-analyzer.hover.memoryLayout.offset
やrust-analyzer.hover.memoryLayout.size
によって変更できます。
許容される値は以下の通りです。
"both"
:「12 (0xC)」の様に表示される"decimal"
:「12」の様に表示される"hexadecimal"
:「0xC」の様に表示される
ニッチ領域が分かるようになった
2023-06-05(v0.3.1541)での変更です。
構造体等のかざし(hover)ヒントにニッチ領域がどれだけあるかを表示できるようになりました。
メモリ領域にどれだけ無駄があるかが分かって便利です。
例えばNonZeroU32
であればniches = 1
、Option<u32>
であればniches = 4294967294
と表示されます。
これは設定rust-analyzer.hover.memoryLayout.niches
にtrue
を指定することで表示させることができます。
安定化されたAPIのドキュメント
安定化されたAPIのドキュメントを独自に訳して紹介します。リストだけ見たい方は安定化されたAPIをご覧ください。
CStr::is_empty
impl CStr { #[inline] #[stable(feature = "cstr_is_empty", since = "1.71.0")] #[rustc_const_stable(feature = "cstr_is_empty", since = "1.71.0")] pub const fn is_empty(&self) -> bool { /* 実装は省略 */ } }
self.to_bytes()
の長さが0ならtrue
を返す。
サンプル
use std::ffi::CStr; let cstr = CStr::from_bytes_with_nul(b"foo\0")?; assert!(!cstr.is_empty()); let empty_cstr = CStr::from_bytes_with_nul(b"\0")?; assert!(empty_cstr.is_empty());
use std::ffi::CStr; use std::ffi::FromBytesWithNulError; fn main() { test().unwrap(); } fn test() -> Result<(), FromBytesWithNulError> { let cstr = CStr::from_bytes_with_nul(b"foo\0")?; assert!(!cstr.is_empty()); let empty_cstr = CStr::from_bytes_with_nul(b"\0")?; assert!(empty_cstr.is_empty()); Ok(()) }
BuildHasher::hash_one
pub trait BuildHasher { #[stable(feature = "build_hasher_simple_hash_one", since = "1.71.0")] fn hash_one<T: Hash>(&self, x: T) -> u64 where Self: Sized, Self::Hasher: Hasher, { /* 既定の実装は省略 */ } }
値一つだけのハッシュを計算する。
これはハッシュを消費する、とりわけハッシュテーブルの実装またはHash
の独自実装が
想定通り動作するかを確認するユニットテストといったコードの利便性を目的としている。
これはHash
の実装などハッシュを生成するようなコードでは使ってはならない。
複数の値から複合されたハッシュを生成する場合、メソッドを繰り返し呼び出して
結果を結合するのではなく、同じHasher
を使ってHash::hash
を複数回呼び出す。
サンプル
use std::cmp::{max, min}; use std::hash::{BuildHasher, Hash, Hasher}; struct OrderAmbivalentPair<T: Ord>(T, T); impl<T: Ord + Hash> Hash for OrderAmbivalentPair<T> { fn hash<H: Hasher>(&self, hasher: &mut H) { min(&self.0, &self.1).hash(hasher); max(&self.0, &self.1).hash(hasher); } } // そしてその型の`#[test]`で・・・ let bh = std::collections::hash_map::RandomState::new(); assert_eq!( bh.hash_one(OrderAmbivalentPair(1, 2)), bh.hash_one(OrderAmbivalentPair(2, 1)) ); assert_eq!( bh.hash_one(OrderAmbivalentPair(10, 2)), bh.hash_one(&OrderAmbivalentPair(2, 10)) );
NonZeroI*::is_positive
impl NonZeroI32 { #[must_use] #[inline] #[stable(feature = "nonzero_negation_ops", since = "1.71.0")] #[rustc_const_stable(feature = "nonzero_negation_ops", since = "1.71.0")] pub const fn is_positive(self) -> bool { /* 実装は省略 */ } }
self
が正であればtrue
を、負であればfalse
を返す。
サンプル
let pos_five = NonZeroI32::new(5)?; let neg_five = NonZeroI32::new(-5)?; assert!(pos_five.is_positive()); assert!(!neg_five.is_positive());
use std::num::NonZeroI32; fn main() { test().unwrap(); } fn test() -> Option<()> { let pos_five = NonZeroI32::new(5)?; let neg_five = NonZeroI32::new(-5)?; assert!(pos_five.is_positive()); assert!(!neg_five.is_positive()); Some(()) }
NonZeroI*::is_negative
impl NonZeroI32 { #[must_use] #[inline] #[stable(feature = "nonzero_negation_ops", since = "1.71.0")] #[rustc_const_stable(feature = "nonzero_negation_ops", since = "1.71.0")] pub const fn is_negative(self) -> bool { /* 実装は省略 */ } }
self
が負であればtrue
を、正であればfalse
を返す。
サンプル
let pos_five = NonZeroI32::new(5)?; let neg_five = NonZeroI32::new(-5)?; assert!(neg_five.is_negative()); assert!(!pos_five.is_negative());
use std::num::NonZeroI32; fn main() { test().unwrap(); } fn test() -> Option<()> { let pos_five = NonZeroI32::new(5)?; let neg_five = NonZeroI32::new(-5)?; assert!(neg_five.is_negative()); assert!(!pos_five.is_negative()); Some(()) }
NonZeroI*::checked_neg
impl NonZeroI32 { #[inline] #[stable(feature = "nonzero_negation_ops", since = "1.71.0")] #[rustc_const_stable(feature = "nonzero_negation_ops", since = "1.71.0")] pub const fn checked_neg(self) -> Option<NonZeroI32> { /* 実装は省略 */ } }
確認済み符号反転(negation)。-self
を計算するが、self == i32::MIN
の場合はNone
を返す。
サンプル
let pos_five = NonZeroI32::new(5)?; let neg_five = NonZeroI32::new(-5)?; let min = NonZeroI32::new(i32::MIN)?; assert_eq!(pos_five.checked_neg(), Some(neg_five)); assert_eq!(min.checked_neg(), None);
use std::num::NonZeroI32; fn main() { test().unwrap(); } fn test() -> Option<()> { let pos_five = NonZeroI32::new(5)?; let neg_five = NonZeroI32::new(-5)?; let min = NonZeroI32::new(i32::MIN)?; assert_eq!(pos_five.checked_neg(), Some(neg_five)); assert_eq!(min.checked_neg(), None); Some(()) }
NonZeroI*::overflowing_neg
impl NonZeroI32 { #[inline] #[stable(feature = "nonzero_negation_ops", since = "1.71.0")] #[rustc_const_stable(feature = "nonzero_negation_ops", since = "1.71.0")] pub const fn overflowing_neg(self) -> (i32, bool) { /* 実装は省略 */ } }
self
を符号反転(negate)するが、値が最小値の場合はオーバーフローする。
オーバーフロー時の挙動に関する文書についてはi32::overflowing_neg
を参照。
サンプル
let pos_five = NonZeroI32::new(5)?; let neg_five = NonZeroI32::new(-5)?; let min = NonZeroI32::new(i32::MIN)?; assert_eq!(pos_five.overflowing_neg(), (neg_five, false)); assert_eq!(min.overflowing_neg(), (min, true));
use std::num::NonZeroI32; fn main() { test().unwrap(); } fn test() -> Option<()> { let pos_five = NonZeroI32::new(5)?; let neg_five = NonZeroI32::new(-5)?; let min = NonZeroI32::new(i32::MIN)?; assert_eq!(pos_five.overflowing_neg(), (neg_five, false)); assert_eq!(min.overflowing_neg(), (min, true)); Some(()) }
NonZeroI*::saturating_neg
impl NonZeroI32 { #[inline] #[stable(feature = "nonzero_negation_ops", since = "1.71.0")] #[rustc_const_stable(feature = "nonzero_negation_ops", since = "1.71.0")] pub const fn saturating_neg(self) -> i32 { /* 実装は省略 */ } }
飽和する符号反転(negation)。-self
を計算するが、self == i32::MIN
の場合はオーバーフローする代わりにMAX
を返す。
サンプル
let pos_five = NonZeroI32::new(5)?; let neg_five = NonZeroI32::new(-5)?; let min = NonZeroI32::new(i32::MIN)?; let min_plus_one = NonZeroI32::new(i32::MIN + 1)?; let max = NonZeroI32::new(i32::MAX)?; assert_eq!(pos_five.saturating_neg(), neg_five); assert_eq!(min.saturating_neg(), max); assert_eq!(max.saturating_neg(), min_plus_one);
use std::num::NonZeroI32; fn main() { test().unwrap(); } fn test() -> Option<()> { let pos_five = NonZeroI32::new(5)?; let neg_five = NonZeroI32::new(-5)?; let min = NonZeroI32::new(i32::MIN)?; let min_plus_one = NonZeroI32::new(i32::MIN + 1)?; let max = NonZeroI32::new(i32::MAX)?; assert_eq!(pos_five.saturating_neg(), neg_five); assert_eq!(min.saturating_neg(), max); assert_eq!(max.saturating_neg(), min_plus_one); Some(()) }
NonZeroI*::wrapping_neg
impl NonZeroI32 { #[inline] #[stable(feature = "nonzero_negation_ops", since = "1.71.0")] #[rustc_const_stable(feature = "nonzero_negation_ops", since = "1.71.0")] pub const fn wrapping_neg(self) -> i32 { /* 実装は省略 */ } }
折り返す(合同、modularな)符号反転(negation)。-self
を計算するが、型の境界で回り込み(wrap around)が起きる。
オーバーフロー時の挙動に関する文書については[i32::wrapping_neg
]を参照。
サンプル
let pos_five = NonZeroI32::new(5)?; let neg_five = NonZeroI32::new(-5)?; let min = NonZeroI32::new(i32::MIN)?; assert_eq!(pos_five.wrapping_neg(), neg_five); assert_eq!(min.wrapping_neg(), min);
use std::num::NonZeroI32; fn main() { test().unwrap(); } fn test() -> Option<()> { let pos_five = NonZeroI32::new(5)?; let neg_five = NonZeroI32::new(-5)?; let min = NonZeroI32::new(i32::MIN)?; assert_eq!(pos_five.wrapping_neg(), neg_five); assert_eq!(min.wrapping_neg(), min); Some(()) }
変更点リスト
言語
raw-dylib
・link_ordinal
・import_name_type
及び-Cdlltool
を安定化- リント
clippy::{drop,forget}_{ref,copy}
の地位向上 - 型推論が制約付き変数周りで保守的になった
- fulfillmentを使用して
Drop
実装の互換性を確認
コンパイラ
PlaceMention
での配置式を評価し、 借用チェッカーに対してlet _ =
パターンの一貫性を高める- Apple系ターゲットに
--print deployment-target
フラグを追加 extern "C-unwind"
とその仲間たちを安定化。 将来のリリースで、既存のextern "C"
などにおける言語を跨ぐ巻き戻しの挙動が変わる可能性がある- ターゲット
*-linux-musl
で使用されるmuslのバージョンを1.2.3に更新し、 32ビットのシステムでtime64を有効化 debugger_visualizer
を安定化。 MicrosoftのNatvisのようにメタデータを組み込める- flatten-format-argsを既定で有効化
Self
がタプルコンストラクタにおける内密性を尊重- 2つの戦略を試してみて良い方の結果を選ぶことによりニッチの配置を改善
aarch64-apple-darwin
で対象のCPUとしてapple-m1
を使用x86_64h-apple-darwin
をTier 3ターゲットとして追加loongarch64-unknown-linux-gnu
をホスト用ツールを伴ってTier 2に格上げ
Rustのティア付けされたプラットフォーム対応の詳細はPlatform Supportのページ(※訳注:英語)を参照
ライブラリ
- 再帰的パニックの処理を再実装。
巻き戻し中での追加のパニックは
Drop
実装を抜ける前にキャッチされるのであれば許容され、 パニック用フック内でのパニックはプログラムが強制終了されるようになった From<&[T]> for Box<[T]>
の境界を(※訳注:T: Copy
から)T: Clone
に緩和Error for mpsc::SendError<T>
とTrySendError<T>
のT: Send
境界を削除alloc::realloc
向け文書を修正し、Layout
における大きさがisize::MAX
を超えてはならないという要件に合致するようにしたstd::thread_local
におけるconst {}
構文を文書化。 この構文はRust 1.59で安定化されたものだが、これまではリリースノートで言及されていなかった
安定化されたAPI
CStr::is_empty
BuildHasher::hash_one
NonZeroI*::is_positive
NonZeroI*::is_negative
NonZeroI*::checked_neg
NonZeroI*::overflowing_neg
NonZeroI*::saturating_neg
NonZeroI*::wrapping_neg
Neg for NonZeroI*
Neg for &NonZeroI*
From<[T; N]> for (T...)
(Nが1..=12の、配列からN要素タプル)From<(T...)> for [T; N]
(Nが1..=12の、N要素タプルから配列)windows::io::AsHandle for Box<T>
windows::io::AsHandle for Rc<T>
windows::io::AsHandle for Arc<T>
windows::io::AsSocket for Box<T>
windows::io::AsSocket for Rc<T>
windows::io::AsSocket for Arc<T>
以下のAPIが定数文脈で使えるようになった。
<*const T>::read
<*const T>::read_unaligned
<*mut T>::read
<*mut T>::read_unaligned
ptr::read
ptr::read_unaligned
<[T]>::split_at
Cargo
- 名前付きdebuginfoのオプションを
Cargo.toml
で許容 cargo metadata
の出力にworkspace_default_members
を追加cargo new
・cargo init
の実行時にワークスペースのフィールドを自動で継承
Rustdoc
互換性メモ
TypeId
から構造的マッチ(structural match)を除去。 パターン内でTypeId
の定数を使用するコードは潜在的に破壊される可能性がある。 既知の事例は既に修正されている。特にlog
クレートにおけるkv_unstable
機能の使用者はlog v0.4.18
以降に更新すること- 標準ライブラリのクレートを表現する
sysroot
クレートを追加。 これは安定版の使用者には影響しないが、独自に標準ライブラリをビルドしているツールでは調整が必要になる可能性がある rustup
下でのCargoの使用を最適化。 Cargoがrustupのプロキシを示すrustc
が実行されることを検知した場合、プロキシを迂回して本来のバイナリを直接使用することを試みる。 rustupとRUSTUP_TOOLCHAIN
の相互作用には仮定があるが、とは言え通常のユーザーには影響しないだろう- Cargoがパッケージを問い合わせる際にスペルミスに対処するため、指定された名前・全部ハイフン・ 全部アンダーバーだけを試すようになった。 これまではハイフンとアンダーバーの全組み合わせを試していたが、crates.ioへの過剰なリクエストを発生させていた
- Cargoが設定テーブルの
[env]
においてRUSTUP_HOME
とRUSTUP_TOOLCHAIN
を拒絶するようになった。 これは問題や混乱を引き起こす可能性が高いためにCargoが対応すべき用例ではない
関連リンク
さいごに
次のリリースのRust 1.72は8/25(金)にリリースされる予定です。 Rust 1.72ではめぼしい新機能はなさそうです(´・ω・`)