あずんひの日

あずんひの色々を書き留めるブログ

Rust 1.71を早めに深掘り

本日7/14(金)にリリースされたRust 1.71の変更点を詳しく紹介します。 もしこの記事が参考になれば記事末尾から活動を支援頂けると嬉しいです。

ピックアップ

個人的に注目する変更点を「ピックアップ」としてまとめました。 全ての変更点を網羅したリストは変更点リストをご覧ください。

タプルと配列を相互に変換できるようになった

FromIntoトレイトによりタプルと配列を相互に変換できるようになりました。 ただし要素数は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.offsetrust-analyzer.hover.memoryLayout.sizeによって変更できます。 許容される値は以下の通りです。

  • "both":「12 (0xC)」の様に表示される
  • "decimal":「12」の様に表示される
  • "hexadecimal":「0xC」の様に表示される

ニッチ領域が分かるようになった

2023-06-05(v0.3.1541)での変更です。

構造体等のかざし(hover)ヒントにニッチ領域がどれだけあるかを表示できるようになりました。 メモリ領域にどれだけ無駄があるかが分かって便利です。 例えばNonZeroU32であればniches = 1Option<u32>であればniches = 4294967294と表示されます。

これは設定rust-analyzer.hover.memoryLayout.nichestrueを指定することで表示させることができます。

NonZeroU32(上)とOption<u32>(下)のニッチ領域
NonZeroU32(上)とOption(下)のニッチ領域

安定化された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(())
}

変更点リスト

言語

コンパイラ

Rustのティア付けされたプラットフォーム対応の詳細はPlatform Supportのページ(※訳注:英語)を参照

ライブラリ

安定化されたAPI

以下のAPIが定数文脈で使えるようになった。

Cargo

Rustdoc

互換性メモ

関連リンク

さいごに

次のリリースのRust 1.72は8/25(金)にリリースされる予定です。 Rust 1.72ではめぼしい新機能はなさそうです(´・ω・`)

ライセンス表記

  • この記事はApache 2/MITのデュアルライセンスで公開されている公式リリースノート及びドキュメントから翻訳・追記をしています
  • 冒頭の画像中にはRust公式サイトで配布されているロゴを使用しており、 このロゴはRust財団によってCC-BYの下で配布されています
  • 冒頭の画像はいらすとやさんの画像を使っています。いつもありがとうございます