RustRover で遭遇する最も一般的な Rust コンパイラーエラー: パート 1
Rust コンパイラーは好き嫌いの激しいツールです。 与えられたソースコードが気に入らなければ、400 種類以上のエラーを出力することもあります(その数は毎月増える一方です!)。 非常にまれなエラーもありますが、毎日のように Rust の開発業務を困難にしているエラーもあります。 このブログ記事の連載では、JetBrains の Rust 専用 IDE である RustRover で開発者が遭遇する最も一般的な Rust コンパイラーエラーメッセージとそれを回避するためのヒントをご紹介します。 まずその前に、「最も一般的なエラー」というのが実際にどういうものなのかを説明していきます。
RustRover の使用データから最も一般的なエラーを特定する
RustRover ユーザーは JetBrains への匿名の使用状況データの送信をオプトインできます。 当社はこのデータを解析することで、さまざまなユーザーパターンを観察して IDE の改善方法に関するインサイトを得ています。 当然ながらユーザーのプライバシーを重視し、IDE では非常に限られた情報しか収集していません。 たとえば、このデータには追跡してユーザーを突き止められるようなものは含まれていません。 データが匿名化されていても、最も発生頻度の高いエラーメッセージのような大まかな IDE の使用状況を把握することはできます。
オプトインしたユーザーが IDE を介して(たとえば、プロジェクトのビルドに必要な実行構成をトリガーするなどによって)Cargo Build コマンドを起動した際に Rust コンパイラーがエラーを出力する場合には必ずエラーコードを記録しています。 この対象にはユーザーがコードを書く際に発生するすべてのコードの問題ではなく、プロジェクトをビルドした後も残っている問題のみが含まれます。 多くの場合、中間エラーは IDE が提供するインスペクションとクイックフィックスを利用することで修正できます。 使用状況データを送信するユーザーと RustRover の使用頻度が増えるほど、より適切にユーザーエクスペリエンスを把握して IDE のコーディング支援機能をさらに改善できるようになります。 使用状況データの提供を通じて RustRover の改善にご協力いただいている皆さんに感謝します!
当社は RustRover を実行しているユーザーからエラーコードを収集し、各エラーコードに遭遇したユーザー数に基づいてエラーをランク付けしました。 この連載の今回の記事では最も一般的なエラーのうち 10 位から 6 位まで説明し、次回の記事では最も一般的なエラーのうち上位 5 件をご紹介します。 これらのエラーの背後にある理由を確認し、単純な例を詳しく見ながら可能な修正方法について説明します。
一般的なエラー第 10 位: E0412(A used type name is not in scope; 使用されている型名はスコープ内にありません)
Rust は型宣言サイトと型名の使用箇所を厳密に区別します。 すべての型名(ジェネリック型を含む)はどこかで宣言し、使用されるスコープ内にある必要があります。 コンパイラーが型名の使用箇所を見つけても、その宣言サイトに関する情報がない場合には E0412 を出力します。 RustRover ユーザーの約 12% がある時点でこのエラーに遭遇していました。
i32
ではなく i42
と入力してしまったとしましょう。 RustRover はこの問題を発見し、不明な型名としてハイライトします。 コンパイラーはより詳細な情報を提供し、コンパイラー出力に Apply fix(修正の適用)ボタンをクリックして簡単に適用可能な修正案を出します。
このほかにも、以下のように E0412 を引き起こす状況がいくつかあります。
- 型を宣言し忘れている。
- 現在のスコープに型をインポートしている。
- ジェネリック型名を導入しているため、型がコンパイラーにアクセスできなくなっている。
この問題を修正するため、型宣言を指定する(構造体を宣言するか、ジェネリック型名を適切に導入する)か、型をスコープに移動する(use
節を使用)ことができます。 E0412 エラーの正式な説明では、このエラーの例が他にも提供されています。
一般的なエラー第 9 位: E0061(An invalid number of arguments was passed when calling a function; 関数を呼び出す際に無効な数の引数が渡されました)
RustRover はこの特定のエラーを認識して一連の修正を提供しますが、RustRover ユーザーの 13% はプロジェクトをビルドする前にこの問題を見過ごしています。
このエラー自体は読んで字の通りです。現在のスコープで宣言されているか、他の場所からインポートされている関数があり、呼び出しサイトで指定されている引数の数が少なすぎるか多すぎるというものです。 例を見ながら RustRover の提案と Rust コンパイラーの提案を比較してみましょう。
この例では、最近多くの人が行っているようにファイルを開いています。 別のプログラミング言語でのコーディングに慣れている方は第 2 引数を指定するかもしれませんが、Rust の場合、このメソッドは引数を 1 つしか取りません。 RustRover と Rust コンパイラーはいずれも第 2 引数を除去するように提案します。 IDE からプロジェクトをビルドすることなく有益な提案を得られるのはとても便利です。 コード内の赤い波線が付いた箇所に注目してみましょう。通常は何かが起ころうとしています!
呼び出そうとしている関数が独自のコードに定義されている場合、この状況はさらに面白くなります。 同じコードサンプルに追加し続けたとしましょう。
この場合、RustRover はこの関数にパラメーターを追加することを最初の代替案として出します。実際、この提案は理にかなっています。 しかし、Rust コンパイラーはパラメーターの削除を要求します。 この違いには根拠があります。 コンパイラーの仕事はプログラムが正しいことを確認することであり、それを達成するには呼び出しサイトの余分な引数を取り除くことが最も簡単なのです。 一方、IDE はユーザーが実現したいものに近づけることを目標としています。 この引数が独自の関数に対して入力されている場合は意図的に行われた可能性が高いため、RustRover はユーザーが着手した作業を完遂できるように支援しようとします。
一般的なエラー第 8 位: E0282(The compiler could not infer a type and asked for a type annotation; コンパイラーが型を推論できず、型アノテーションを要求しました)
変数に必要な型を判断できず、手動で型アノテーションを追加することしか提案できない状況では、コンパイラーが行き詰まることもあります。 このエラーに遭遇したことがある方は多く、RustRover ユーザーの 13.5% が経験しています。
E0282 のようなエラーの主な原因は汎用性にあります。 多くのライブラリ関数はジェネリック型パラメーターを取りますが、コンパイラーはこのようなパラメーターを具象型にインスタンス化しなければならないため、混乱してしまいます。 次の例を見てみましょう。
ここでは、文字列の数字をあるコンテナーに収集しようとしています。 残念ながら、コンパイラーはその数字がどのような種類であるのか、どのようなコンテナーであるのかを判定できません。
コンパイラーはコンテナーの型を先に指定するように提案しますが、 この修正を適用した場合は同じようなエラーが再発してしまいます。今度は str::parse
に関するエラーです。 collect
と parse
はいずれもジェネリックメソッドですが、コンパイラーがこれらを使用するコードをコンパイルするには、正確な型を知る必要があります。 RustRover ではエラーを発見できるように型チェック機能の動作を改良している最中であるため、エラーはハイライトされませんので注意してください。
この問題を修正するにはいくつかの方法があります。型アノテーションを追加する場所が 2 つ以上あるためです。 numbers
ベクターの具象型を指定することができます。
let numbers: Vec = "1 5 6 3"
または、collect
を呼び出す際に同じ型を記述することも可能です。
.collect::<Vec>();
最後に、別々の場所で異なる型を記述することができます。
let numbers = "1 5 6 3" .split_whitespace() .map(str::parse::) .map(Result::unwrap) .collect::<Vec<_>>();
このエラーは簡単に修正できます。必要な型を指定するだけです。
一般的なエラー第 7 位: E0432(An import was unresolved; インポートが解決されませんでした)
RustRover には多数の自動補完機能が備わっています。 たとえば、コードに正規表現を導入してみましょう。
最初の候補を選択すると、補完自体に加えて以下の 2 つが発生します。
regex
クレートの依存関係が Cargo.toml に追加される。use regex::Regex;
節がファイルの最上部に追加される。
このように use 節を追加すると、インポートが自動的に正しく記述されます。 ただし、インポートを手動で書く必要がある場合もあり、そこで E0432 エラーが発生する可能性があります。RustRover ユーザーの 15.5% はこのエラーに時々遭遇しています。主にクレートまたはモジュールの名前にスペルミスがある場合、存在しないものをインポートしようとした場合、または誤った use
節をどこかからコピーしてコードに貼り付けた後に発生しています。 どんな場合であれ、依存関係とその名前を確認することが真っ先に提案されます。
RustRover によってこのエラーを防げる場合もあります。 インポートしようとしているクレートを RustRover が認識している場合、外部ソースからコードを貼り付ける際に依存関係を追加するように提案されるか、次のクイックフィックスによるサポートが提供されます。
対応する依存関係を Cargo.toml に追加すると、すぐにエラーが修正されます。 クレートが使用できるようになったら、名前による他の問題を回避するため、use
節に含まれる他のパス部分に自動補完を使用するのがより安全と言えます。 ある名前が使用できるかどうかは、有効化されているクレートの機能によって異なる場合があることにも注意してください。
super
や crate
などの特殊なパス名に関しては、特に異なる Rust エディション間で処理方法が違う場合などに問題が生じることもあります。 この件に関する詳細は、正式な説明をご覧ください。
一般的なエラー第 6 位: E0832(A variable was used after its contents have been moved elsewhere; 変数の内容が他の場所に移された後にその変数が使用されました)
これは興味深いエラーであり、所有権に関わる問題です! RustRover ユーザーの 17% がこのエラーに遭遇しています。 正式な説明では、多数の例を使って詳しく説明されています。 残念ながら、これに関しては RustRover はあまり役に立ちません。 外部リンターが無効化されている場合、RustRover の内部的な仕組みでは次のコードの問題は認識されません。
fn main() { let vec = vec![1, 2, 3, 4, 5]; let mut sum = 0; for v in vec { sum += v; } println!("Sum of {vec:?} elements is {sum}"); }
この無害に見えるコードは、他のいくつかのプログラミング言語ではまったく問題ないかもしれません。 ベクターがあり、その要素の合計を計算しようとしています。 たとえば C の経験者は反復子を使った高度な関数型プログラミング手法を知らないため、代わりに従来の for
ループを記述します。 すべての合計が完了したら、ベクターと計算の結果を出力するだけです。 ですよね?
ただ、Rust ではそうはいきません。所有権ルールと言われるものがあるためです。
ここで問題となっているのは、for
ループのデータのソースが into_iter()
呼び出しに拡張されており、これがベクター全体の所有権を取っていることです。 したがって、println!
でベクターの要素にアクセス使用する頃には、コンパイラーが移動されたと不満を示します。
修正は簡単で、コンパイラーによって提案されます。所有権がループに移動されないように &vec
を反復処理して所有権を借りる方法です。
一般的には値の所有権を常に追跡するのが便利です。 値の移動と値の貸し出しは Rust の基本概念です。 これを理解することがすべての学習者の最初の課題と言えます。
まとめ
この連載の最初の記事では、RustRover で提供される使用状況データに基づく最も一般的な Rust コンパイラーエラーを定義し、10 位から 6 位までのエラーについて説明しました。 次の記事では、最も一般的なエラーの上位 5 位を詳しく見ながら、すべての Rust 開発者の念頭にある「どの部分の Rust が一番問題を起こしやすいのか」という疑問にお答えしたいと思います。
オリジナル(英語)ブログ投稿記事の作者: