あずんひの日

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

Rust 1.74を早めに深掘り

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

ピックアップ

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

演算を飽和させるためのSaturating型が使えるようになった

各種演算で飽和させる事ができるSaturating<T>型が使えるようになりました。

これまでは演算ごとにhoge.saturating_add(fuga)foo.saturating_mul(bar)といったメソッド呼び出しをする必要がありました。 これは一連の演算の中で1つだけあるというのであれば良いのですが、複数あると煩雑で読みやすさが激減します。

そこで値をSaturating<T>型に包む事で、自然な形の演算でも結果を飽和させることができるようになります。 両辺共にSaturating<T>の必要があるため、先に包んだものを代入しておくと良いでしょう。

use std::num::Saturating;

// saturating_*メソッドを使う形
fn without_saturating(x: u32) -> u32 {
    let y = 100;
    let z = 200;
    x.saturating_mul(y).saturating_add(z)
}

// Saturating型を使う型
fn with_saturating(x: u32) -> u32 {
    let x = Saturating(x);
    let y = Saturating(100);
    let z = Saturating(200);
    (x * y + z).0
}

依存クレートのコンパイル失敗時にもコンパイルを極力続行可能に

cargoに--keep-goingフラグが追加され、依存関係における一部クレートのコンパイルに失敗しても、 (そのクレートに依存しない)他クレートのコンパイルを続行できるようになりました。

これはmake-k--keep-going)に相当するもので、「エラーが出るのは分かっているが取り敢えずやれるだけコンパイルしておきたい」という場合に使えます。

Cargo.tomlにリントの設定を書けるようになった

lib.rsmain.rs#![allow(lint)]などを書く代わりに、Cargo.toml[lints]テーブルにリントの設定を書くことができるようになりました。 単体クレートの場合はそこまで威力はありませんが、ワークスペースの場合は設定を共有できるため便利です。

この設定はlintsテーブル以下に各ツールのテーブルを書きます。 例えばRust標準のリントはlints.rustに、Clippyのものはlints.clippyに書きます。 ツール名は、Rust標準以外は#[allow(tool::lint)]toolの部分に当たります。

また、ワークスペースで使う場合は他のフィールド同様、各クレートにlints.workspace = trueの記述が必要です。

各ルールはrule = "deny"のように書きます。 優先度も同時に指定するrule = { level = "deny", priority = -1 }という書き方もできます。 これはグループに指定するのが意図された使い方のようです。

単体クレートの場合

# Cargo.toml
[package]
name = "hoge"
version = "0.1.0"
edition = "2021"

[lints.rust]
dead_code = "deny"

ワークスペースの場合

# Cargo.toml
[workspace]
members = ["hoge"]

[workspace.lints.clippy]
all = { level = "allow", priority = -1 }
perf = "deny"
# hoge/Cargo.toml
[package]
name = "hoge"
version = "0.1.0"
edition = "2021"

[lints]
workspace = true

rustdocに警告用ブロックを書けるようになった

rustdocに目立つ警告用文言を書けるようになりました。 これはGitHub等で導入されているMarkdownの拡張文法ではなく、自分でHTMLタグ(<div class="warning">)を書く形式で実装されています。

拡張文法を採用しなかった点について、 個人的にはGitHubの拡張文法では引用でもないのに引用記法を流用している(当初は出力としてもblockquote要素が使われていた)のがどうにも嫌なため、 それに比べれば書きにくくはあるもののあべこべでない分こちらの方が好みです。

/// 色々やる関数。
///
/// <div class="warning">この文書は見本です。</div>
pub fn hoge() {}
警告用ブロックの例
警告用ブロックの例

最近のrust-analyzer

最近rust-analyzerに入った変更の中から個人的に気になったものをピックアップしました。

変数への切り出しが範囲選択不要になった

2023-10-30 (v0.3.1713)での変更です。

関数呼び出しなどにカーソルがあるとき、その結果を変数に切り出すことができるようになりました。 これまでは関数呼び出し全体を範囲選択する必要があり、少し不便でした。

関数呼び出しを選択した状態のコードアクション(左)と選択しない状態のコードアクション(右)
関数呼び出しを選択した状態のコードアクション(左)と選択しない状態のコードアクション(右)

不変版トレイト実装から可変版実装を生成できるようになった

2023-11-06 (v0.3.1722)での変更です。

トレイトstd::ops::Indexの実装からトレイトstd::ops::IndexMutの実装を生成できるようになりました。 現段階ではIndexからIndexMutの生成だけですが、将来的にはそれ以外のトレイトも生成できるようになるでしょう。

定義へ飛ばずに構造体の中身を確認できるようになった

2023-11-13 (v0.3.1730)での変更です。

構造体のかざし(hover)ヒントで構造体のフィールドを確認できるようになりました。 定義に飛ばずとも内容が分かるため便利そうです。

構造体の中身をかざしヒントで確認した様子
構造体の中身をかざしヒントで確認した様子

安定化されたAPIのドキュメント

安定化されたAPIのドキュメントを独自に訳して紹介します。リストだけ見たい方は安定化されたAPIをご覧ください。

std::num::Saturating

原典

#[stable(feature = "saturating_int_impl", since = "1.74.0")]
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default, Hash)]
#[repr(transparent)]
#[rustc_diagnostic_item = "Saturating"]
pub struct Saturating<T>(
    #[stable(feature = "saturating_int_impl", since = "1.74.0")] pub T,
);

Tに対し、あえて飽和する演算を提供する。

u32の値に対する+のような演算は決してオーバーフローしないように設計されており、 同時に一部のデバッグ構成ではオーバーフローが検知されパニックする。 ほとんどの演算はこの部類ではあるが、中には飽和演算を明確に期待して頼るコードもある。

飽和演算はsaturating_addのようなメソッド、あるいはSaturating<T>型によって使用可能である。 後者は内包する値に対するあらゆる標準的な算術演算が飽和するという意味論である事を意図していると言える。

内包する値はSaturatingタプルの.0インデックスを使用して取得できる。

サンプル

use std::num::Saturating;

let max = Saturating(u32::MAX);
let one = Saturating(1u32);

assert_eq!(u32::MAX, (max + one).0);

OsString::from_encoded_bytes_unchecked

原典

    #[inline]
    #[stable(feature = "os_str_bytes", since = "1.74.0")]
    pub unsafe fn from_encoded_bytes_unchecked(bytes: Vec<u8>) -> Self
    { /* 実装は省略 */ }
}

バイト列を、OsStr用にエンコードされた有効なデータを含んでいるかどうかを確認せずにOsStringに変換する。

バイト列の符号化方式は未規定で、環境依存で、UTF-8の自己同期的スーパーセットである。 UTF-8の自己同期的スーパーセットであることから、この符号化方式は7ビットASCIIのスーパーセットでもある。

ネイティブ表現との安全でクロスプラットフォームな変換についてはモジュールの最上位文書を参照のこと。

安全性

符号化方式が未規定であるため、呼び出し元は検証済みUTF-8や同一のターゲットプラットフォーム向けにビルドされた 同一のRustバージョンにおけるOsStr::as_encoded_bytesから返されるバイト列の混合からなるバイト列を渡す必要がある。 例えばネットワーク経由で送信されたバイト列やファイルに保存されたバイト列からOsStringを再構築することは この安全性規則に違反するだろう。

符号化方式が自己同期的であることにより、 OsStr::as_encoded_bytesから返されるバイト列は有効な空でないUTF-8の部分文字列の直前直後へ分割できる。

サンプル

use std::ffi::OsStr;

let os_str = OsStr::new("Mary had a little lamb");
let bytes = os_str.as_encoded_bytes();
let words = bytes.split(|b| *b == b' ');
let words: Vec<&OsStr> = words.map(|word| {
    // 安全性:
    // - 各`word`は`OsStr::as_encoded_bytes`に由来する内容のみを含む
    // - 空でないUTF-8の部分文字列であるASCIIの空白文字でのみ分割する
    unsafe { OsStr::from_encoded_bytes_unchecked(word) }
}).collect();

OsString::into_encoded_bytes

原典

    #[inline]
    #[stable(feature = "os_str_bytes", since = "1.74.0")]
    pub fn into_encoded_bytes(self) -> Vec<u8>
    { /* 実装は省略 */ }
}

OsStringからバイト列のスライスに変換する。バイト列のスライスをOsStringに戻すには、 OsStr::from_encoded_bytes_unchecked関数を使用する。

バイト列の符号化方式は未規定で、環境依存で、UTF-8の自己同期的スーパーセットである。 UTF-8の自己同期的スーパーセットであることから、この符号化方式は7ビットASCIIのスーパーセットでもある。

注意:符号化方式が未規定であるため、有効なUTF-8でないあらゆるバイト列のサブスライスはあやふやなものとして扱われるべきであり、 また同一のターゲットプラットフォーム向けにビルドされた同一のRustバージョン内でのみ比較されるべきである。 例えばネットワーク経由で送信されたバイト列やファイルに保存されたバイト列は非互換となる可能性が高い。 符号化方式の詳細についてはOsStringを、環境依存の指定された変換についてはstd::ffiを参照のこと。

OsStr::from_encoded_bytes_unchecked

原典

    #[inline]
    #[stable(feature = "os_str_bytes", since = "1.74.0")]
    pub unsafe fn from_encoded_bytes_unchecked(bytes: &[u8]) -> &Self
    { /* 実装は省略 */ }
}

バイト列のスライスを、OsStr用にエンコードされた有効なデータを含んでいるかどうかを確認せずにOS文字列のスライスに変換する。

バイト列の符号化方式は未規定で、環境依存で、UTF-8の自己同期的スーパーセットである。 UTF-8の自己同期的スーパーセットであることから、この符号化方式は7ビットASCIIのスーパーセットでもある。

ネイティブ表現との安全でクロスプラットフォームな変換についてはモジュールの最上位文書を参照のこと。

Safety

符号化方式が未規定であるため、呼び出し元は検証済みUTF-8や同一のターゲットプラットフォーム向けにビルドされた 同一のRustバージョンにおけるOsStr::as_encoded_bytesから返されるバイト列の混合からなるバイト列を渡す必要がある。 例えばネットワーク経由で送信されたバイト列やファイルに保存されたバイト列からOsStrを再構築することは この安全性規則に違反するだろう。

符号化方式が自己同期的であることにより、 OsStr::as_encoded_bytesから返されるバイト列は有効な空でないUTF-8の部分文字列の直前直後へ分割できる。

サンプル

use std::ffi::OsStr;

let os_str = OsStr::new("Mary had a little lamb");
let bytes = os_str.as_encoded_bytes();
let words = bytes.split(|b| *b == b' ');
let words: Vec<&OsStr> = words.map(|word| {
    // 安全性:
    // - 各`word`は`OsStr::as_encoded_bytes`に由来する内容のみを含む
    // - 空でないUTF-8の部分文字列であるASCIIの空白文字でのみ分割する
    unsafe { OsStr::from_encoded_bytes_unchecked(word) }
}).collect();

OsStr::as_encoded_bytes

原典

    #[inline]
    #[stable(feature = "os_str_bytes", since = "1.74.0")]
    pub fn as_encoded_bytes(&self) -> &[u8]
    { /* 実装は省略 */ }
}

OS文字列からバイト列のスライスに変換する。バイト列のスライスをOS文字列のスライスに戻すには、 OsStr::from_encoded_bytes_unchecked関数を使用する。

バイト列の符号化方式は未規定で、環境依存で、UTF-8の自己同期的スーパーセットである。 UTF-8の自己同期的スーパーセットであることから、この符号化方式は7ビットASCIIのスーパーセットでもある。

注意:符号化方式が未規定であるため、有効なUTF-8でないあらゆるバイト列のサブスライスはあやふやなものとして扱われるべきであり、 また同一のターゲットプラットフォーム向けにビルドされた同一のRustバージョン内でのみ比較されるべきである。 例えばネットワーク経由で送信されたスライスやファイルに保存されたスライスは非互換となる可能性が高い。 符号化方式の詳細についてはOsStringを、環境依存の指定された変換についてはstd::ffiを参照のこと。

io::Error::other

原典

    #[stable(feature = "io_error_other", since = "1.74.0")]
    pub fn other<E>(error: E) -> Error
    where
        E: Into<Box<dyn error::Error + Send + Sync>>,
    { /* 実装は省略 */ }
}

任意のエラーペイロードから新しいI/Oエラーを生成する。

この関数はOS自体に起因しないI/Oエラーを汎用的に生成するために使用される。 これはError::newErrorKind::Otherを指定するショートカットである。

サンプル

use std::io::Error;

// エラーは文字列から生成できるし、
let custom_error = Error::other("oh no!");

// その他エラーからも生成できる
let custom_error2 = Error::other(custom_error);

変更点リスト

言語

コンパイラ

ライブラリ

安定化されたAPI

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

Cargo

Rustdoc

互換性メモ

内部の変更

これらの変更がユーザーに直接利益をもたらすわけではないものの、コンパイラ及び周辺ツール内部では重要なパフォーマンス改善をもたらす。

この周期には無い。

関連リンク

さいごに

次のリリースのRust 1.75は12/29(金)にリリースされる予定です。 Rust 1.75では皆さん待望の、トレイトにおけるasync fn及び戻り値のimpl Traitが使えるようになる予定です。

ライセンス表記

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