本日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_emptyBuildHasher::hash_oneNonZeroI*::is_positiveNonZeroI*::is_negativeNonZeroI*::checked_negNonZeroI*::overflowing_negNonZeroI*::saturating_negNonZeroI*::wrapping_negNeg 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_unalignedptr::readptr::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ではめぼしい新機能はなさそうです(´・ω・`)