あえて言おう。Rustが大好きであると。
この記事はRust 2 Advent Calendar 2020 の8日目の記事です。
近年、Rustの人気が上がってきているようで、インターネット上でも、「Rust 好き」等で検索するとさまざまな記事が見つかる反面、逆に「Rust 嫌い」というワードで出る記事が少ないのが不公平な気もするので、あえてRustの良くないなと思う点を挙げてみました。
非同期処理について
Rustにはビルトインの非同期処理があります。これは大きく分けて2つの要素から成り立っています。
-
Future
トレイト - ランタイム
詳しくは他の記事を参照していただきたいのですが、あまり正確でない説明をすると、1. のFuture
クレイトは非同期処理自体を定義するためのトレイトです。例えば、Rustの組み込みキーワードasync
で定義された関数
async fn func() -> Result<(), Error> { /* snip */ }
は、
fn func() -> impl Future<Output = Result<(), Error>> { /* snip */ }
ような感じに変換されます。この時Future
トレイトが使われています。しかしながら、ここでfunc()
を
let _ = func();
としても、func()
の中身は実行されません。Future
トレイトを実装したオブジェクトが生成されるだけです。
実際にFuture
の処理を実行するためには、2. のランタイムが必要であり、これはFuture
トレイトと違ってRustの標準ライブラリには含まれません。
よって、ランタイムを実装した外部トレイトに依存する必要があります。
現在、Rustで広く使われているランタイムには(私が知っている限り)以下のものがあります1。
-
tokio
0.2系 -
tokio
0.3系 -
tokio
1.0系 (←追加) futures
例えば、tokio
0.2系で作られたFuture
をtokio
0.3系やfutures
のランタイムで実行することは基本的にできません。
これが特に問題となるのはasync fn
を含む外部クレートを利用する場合で、その外部クレートがサポートするランタイムがどれかを利用者が注意深く調べておく必要があります。tokio
0.3系のランタイムを使用したくても、依存するクレートがtokio
0.2系に依存している場合は、0.3系を使うことができません。
現在は移行期であり、今後tokio
0.3系に対応するクレートが増えてくると思いますが、完全に移行し切るまでは、クレートのインストール時に動でfeatures
を指定する等する必要があるでしょう。
使えるトレイトが多くて混乱する
トレイトは、Rustのすばらしい機能の一つです。逆に、Rustの機能の多くはトレイトに依存しているので、トレイトを使いこなせなければRustを便利に活用することは難しいでしょう。
しかし、トレイトには多くの種類があるため、トレイトの全貌を知るのは簡単ではありません。特に、似たような機能が複数のトレイトで提供されている場合があり、ドキュメントを読んでもどう使い分ければいいか理解できないことが多いです。例えば、以下のトレイトはすべて「型の変換」に係るトレイトです。
From
Borrow
AsRef
ToOwned
Deref
この中で、Deref
はスマートポインタに対して演算子*
を適用したときの動作をオーバーライドするトレイトであり、使用には注意が必要です。
Borrow
とAsRef
については以下を参照。
関数の引数と所有権
情報提供: @Iwancof_ptrさん
例えば以下のコードはエラーになります。なぜでしょうか?
struct S;
impl S {
pub fn outer_ref(&self, _: i32) {
}
pub fn outer_mut(&mut self, _: i32) {
}
pub fn inner_ref(&self) -> i32 {
10
}
pub fn inner_mut(&mut self) -> i32 {
10
}
}
fn main() {
let mut s = S;
s.outer_ref(s.inner_mut());
}
ここで、outer_ref()
呼び出しを以下のように展開してみるをわかりやすいと思います。
S::outer_ref(&s, S::inner_mut(&mut s));
これを更に分解するとこのようになります。
- 変数
s
の参照&s
を取得する。 - 変数
s
の参照&mut s
を取得する。 -
&mut s
を引数としてS::inner_mut()
を呼び出し値10
を得る。 -
&s
及び10
を引数としてS::outer_ref()
を呼び出す。
ここで、2. から3. までの間、変数s
に対する参照&s
及び&mut s
が共存しています。これがエラーの原因だと思われます。
よって、以下のように書けばエラーは無くなります。
fn main() {
let mut s = S;
let i = s.inner_mut();
s.outer_ref(i);
}
また、試したところs.outer_mut(s.inner_ref())
はエラーになりませんでした。原因がわかる方教えてください。(追記: Two-phase borrow
によるものとの事です。)
https://t.co/rvZq7Mj6T0
— いわんこ (@Iwancof_ptr) November 21, 2020
こんな感じになりました。
うーん。どちらかを mut にすると借用できないっぽいですね。
(追加) メソッド呼び出し構文のaudo-dereferenceについて
Rustでは、型X
のオブジェクトx
に対してメソッドm
を呼び出す際に、X::m(x)
もしくはx.m()
という構文を用いることができます。しかし、後者の場合、自動Deref機能が働き、以下の例のように本来(&x).m()
や(*x).m()
と書くべきところで*
や&
を省略することができます。
// ---- & の例 ----
struct X;
impl X {
fn m(&self) {}
}
let x = X;
X::m(&x);
(&x).m(); // 上に同じ
// ここで&は下のように省略できる
X.m();
// ---- * の例 ----
let y = Box::new(1.5f64);
println!("{}", f64::ceil(*y));
println!("{}", (*y).ceil()); // 上に同じ
// ここで*は以下のように省略できる
println!("{}", y.ceil());
これは便利な機能ですが、時折互換性の問題を起こすという問題があります。
例えば、IntoIterator
というトレイトがあります。これは、vec![1,2,3].into_iter()
のようにinto_iter()
という関数を提供してイテレータを生成するためのトレイトです。これを配列[T; N]
に実装しようという議論があります。
Add IntoIterator
impl for arrays by value (for [T; N]
)
しかし、ここで問題なのは、すでに&[T; N]
に対してIntoIterator()
が実装されている点です。すなわち、以下のようなコードは現時点でも合法2で、<&[i32; 3] as IntoIterator>::into_iter(&arr)
がよばれます。そのため、ループ内で使える変数i
は&i32
型です。
let arr = [1, 2, 3i32];
for i in arr.into_iter() {
println!("{}", i); // i は &i32
}
一方、[T; N]
に対してIntoIterator
を実装すると、上のコードでは代わりに<[i32; 3] as IntoIterator>::into_iter(arr)
が呼ばれることになり、変数i
の型はi32
となります。これは明らかに互換性を侵害しています。
実際、上記コードをコンパイルすると以下のような警告が表示されます。
|
3 | for i in arr.into_iter() {
| ^^^^^^^^^ help: use `.iter()` instead of `.into_iter()` to avoid ambiguity: `iter`
|
= note: `#[warn(array_into_iter)]` on by default
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #66145 <https://github.com/rust-lang/rust/issues/66145>
現状の最新のエディションはedition=2018
ですが、近い将来のエディションでは解消されるのではないでしょうか。
#rustlang [T; N] に IntoIterator<Item=T>を実装してほしい。
— yasuo_ozu@FizzBuzzむずい (@yasuo_ozu) December 28, 2020
&[T; N]に対するIntoIter<Item=&T>はすでに実装されてるけど。