かうなってて欲しかった Rust

Rust は良い言語ですが、それでも書いてるうちに不満を感じることもあります。この記事では、Rust の言語仕様がこんな風になってたら良かったのにといふ個人的な願望を垂れ流してみます。
(記載の内容は Rust 1.84.0, Edition 2021 の仕様に基づいてゐます。)

# must_use はデフォルトであって欲しかった

Rust では、函数を呼び出した後にその戻り値を使はなかった時などに「値を使ふのを忘れてるよ」といふコンパイル時警告を発動させるために `#[must_use]` といふ属性が使はれてゐます。この警告自体は有用ですが、函数 (もしくは構造体などのデータ型) を定義するたびにいちいち `#[must_use]` 属性を付けて回らないといけません。自分の感覚だと、この属性を付けないことの方が少ないので、「警告を出させるために属性を付ける」のではなく「警告を出させないために属性を付ける」方が楽だったのではないかと思います。

ちなみに「警告を出させないために属性を付ける」タイプの最近の言語としては Swift があります。

```rust:今の Rust だと
// 戻り値を無視しても警告されない函数
fn f() -> i32 {
    0
}

// 戻り値を無視すると警告される函数
#[must_use]
fn g() -> i32 {
    1
}
```

```rust:私の願望
// 戻り値を無視しても警告されない函数
#[maybe_unused]
fn f() -> i32 {
    0
}

// 戻り値を無視すると警告される函数
fn g() -> i32 {
    1
}
```

# パニックし得る箇所をもっと明示的にして欲しかった

`Option` から値を取り出すときには (パターンマッチングをしない場合は) `unwrap` とか `unwrap_or` とか `unwrap_or_default` とかのメソッドを使ひますが、このうち `unwrap` は値が無い時に直ちにパニックするもので、他の `unwrap_or...` 系メソッドは値が無い時に代はりの値を返します。パニックを起こし得る操作は他の操作よりもより注意深く扱ひたいものですが、メソッド内でパニックが起き得るかどうかはメソッドの命名パターンからは読み取れず区別できません。パニックを起こし得る `unwrap` が他の `unwrap_or...` 系メソッドよりも短い名前になってゐるのは、潜在的危険性のある方の処理をより呼び出しやすくしてゐる様で、プログラマーにフレンドリーではない気がします。

Rust には、同じ操作に対して失敗時にパニックする API と `Option::None` や `Result::Err` を返す API とが両方用意されてゐる場合がちらほらあります。例へば、配列スライスに対する `&a[i]` と `a.get(i)` とか、`Vec` に対する `reserve` と `try_reserve` とかです。かういふのは、API を使ふ側からすると適切な方を吟味して選ぶ手間がかかるし、API を実装する側からすると複数の似た様な API を実装するといふ面倒があります。

また、 `Vec::remove` の様にパニックするバージョンしか API が用意されてゐないものもあり、使ふ側の設計の自由度や簡潔さを下げます。

この様ないまいちさの元を糺すと、操作がパニックを起こし得るかどうかが型では分からないために、Rust の持つ強力な型システムを意図しないパニックの防止に活かすことができてゐないといふことが分かります。私としては、失敗し得る操作は全て `Option` または `Result` を返すように統一し、パニックは必要に応じて呼び出し元で発生させる様な設計思想が良かったのではないかと思ひます。少なくともライブラリが自分でパニックしない様にすれば、それを呼び出す側のプログラマーが明示的にパニックを起こさせない限り、パニックは起きなくなるはずです。 (「起きなくなる」は言ひすぎかもしれませんが、少なくともどこでパニックが起き得るのかはコード上でもっと明確になるのではないでせうか。)

Rust では `!` は前置演算子としてもう使はれてしまってゐますが、それをいったん無視すると、 `?` と同様に `!` を後置演算子として活用する言語仕様もあり得たと思ひます。 `?` は「`Option` または `Result` の中身が失敗側だったら直ちに `return` する」といふ効果を持ちますが、それと似た様なノリで `!` に「`Option` または `Result` の中身が失敗側だったら直ちにパニックする」といふ動作を与へれば、必要に応じてわざとパニックを引き起こさせるのも簡単にできます。

```rust:今の Rust だと
let a = [1, 2, 3];
let b: i32 = a[1];
let c: Option<&i32> = a.get(3);

let mut v = Vec::<i32>::new();
v.reserve(3);
let r: Result<(), TryReserveError> = v.try_reserve(99999999999999999);
```

```rust:私の願望
let a = [1, 2, 3];
let b: i32 = a[1]!;
let c: Option<&i32> = &a[3];

let mut v = Vec::<i32>::new();
v.reserve(3)!;
let r: Result<(), TryReserveError> = v.reserve(99999999999999999);
```

# unsafe はもっと細分化されてて欲しかった

Rust でコードを書くにあたって unsafe なものに関はらずに済むに越したことはありませんが、実現したいことによっては unsafe な何かに触れざるを得ないこともあります。ただ、今の Rust だと unsafe といふキーワードを一つ付けるだけであらゆるヤバいことが無制限に許可されてしまひます。個人的には、許可される危険な操作の種類を細分化して、個別に許可を得る風になっててくれたら嬉しいです。その方が、自分が何をしようとしてゐる (あるいはしてゐない) のかについてプログラマーがより自覚的になり、コンパイラーに意図をより正確に伝へ、より厳密な型検査ができる様になると思ひます。

```rust:今の Rust だと
use std::mem::MaybeUninit;
fn main() {
    let mut a = MaybeUninit::<i32>::uninit();
    unsafe { a.as_mut_ptr().write(42); }
    let b = unsafe { a.assume_init() };
    println!("{b}");
}
```

```rust:私の願望
use std::mem::MaybeUninit;
fn main() {
    let mut a = MaybeUninit::<i32>::uninit();
    #[allow(unsafe_pointer_dereference)]
    { a.as_mut_ptr().write(42); }
    #[allow(unsafe_assume_init)]
    let b = a.assume_init();
    println!("{b}");
}
```

上の例だととりあへず `unsafe_pointer_dereference` (ポインターの参照外し) と `unsafe_assume_init` (初期化されてないかもしれない値を初期化済みだと言ひ張る) の二種類を挙げてみましたが、unsafe な演算の分類としては他にも `unsafe_pointer_arithmetic` (ポインター演算) とか `unsafe_static_access` (静的変数へのアクセス) とかが考へられさうです。

上記の説明だけだといまいち有用性がお分かりいただけないかもしれませんが、例へば上の例を下の様に書き換へるとコンパイル時エラーになって欲しいといふのが私の想定です。

```rust:これはエラー
use std::mem::MaybeUninit;
fn main() {
    let mut a = MaybeUninit::<i32>::uninit();
    #[allow(unsafe_pointer_dereference)]
    { *a.as_mut_ptr() = 42; } // ← この行を書き換へた
    #[allow(unsafe_assume_init)]
    let b = a.assume_init();
    println!("{b}");
}
```

なぜこれがエラーなのか? それは `write` 函数と代入演算子の意味論の違ひによります。代入演算子は、「そこにある値を drop した後、新しい値をそこに移す」といふ動作をします。つまり、 `*a.as_mut_ptr() = 42` は以下のコードと等価です。[^assume_init_drop]

[^assume_init_drop]: 「そこにある値を drop」の部分は `a.assume_init_drop()` ではなく直接 `std::ptr::drop_in_place(a.as_mut_ptr())` を実行する方が正確な翻訳ですが、`unsafe_assume_init` との関連を分かりやすくするためにごまかしました。

```rust:代入演算子の動作の中身
a.assume_init_drop();
a.as_mut_ptr().write(42);
```

しかし、先ほどのコードでは `*a.as_mut_ptr() = 42` の部分には `allow(unsafe_pointer_dereference)` だけが注釈されてゐて `allow(unsafe_assume_init)` は付いてゐませんから、 `assume_init_drop` 函数の呼び出しに相当する部分が許可されずエラーになるといふ訣です。これで、ポインターに `write` 函数を使ふべきところでうっかり代入演算子を使ってしまふといふありがちな間違ひを捕へることができました。[^ありがち][^copy]

[^ありがち]: この間違ひが「ありがち」かどうかは人によるかもしれませんが。
[^copy]: この例だと `i32` が `Drop` を実装してゐないのでこの間違ひは特に問題にはならないのですが、`MaybeUninit` の中身が `Drop` を実装してゐるもの (`Vec` とか) だと本当に未定義動作になります。

実際に上の様なことをしようとすると、unsafe な操作を正確に分類することが実際には相当難しさうだと想像されます。しかしそれこそが unsafe な世界が持つ複雑さの一端であり、現状その複雑さの相手をすることはすべてプログラマーに任されてゐます。そこをもう少し言語やその処理系の側から手助けしてくれる様になってゐたらなぁと思ひます。

# `値.into::<変換先型>()` と書きたかった

`Into` トレイトの型パラメーターはメソッドではなくトレイト自体に付いてゐます。

```rust:今の Rust だと
pub trait Into<T>: Sized {
    fn into(self) -> T;
}

impl<T, U> Into<U> for T
where
    U: From<T>,
{
    fn into(self) -> U {
        U::from(self)
    }
}
```

これの微妙に不便なところは、into といふ英単語の自然な用法に従って `into` の後に変換先の型名を書かうとしても書けないといふ点です。例えば `&str` を `String` に変換しようとするときに `let x = "foo".into::<String>();` みたいには書けません。

`Into` トレイトが以下の様に定義されていたら、その様な書き方もできたのですが……。

```rust:私の願望
pub trait Into: Sized {
    fn into<T>(self) -> T
    where
        T: From<Self>;
}

impl<T> Into for T {
    fn into<U>(self) -> U
    where
        U: From<Self>,
    {
        U::from(self)
    }
}
```

----

他にも何か思ひ付いたら後で書き足します。