RustRover で遭遇する最も一般的な Rust コンパイラーエラー: パート 2
このブログ連載記事のパート 1 では RustRover の使用状況データに基づいて最も一般的な Rust コンパイラーエラーの上位 10 件に目を向け、その中の半分のエラーについて取り上げました。 このパートでは最も頻繁に発生するエラーのうち上位 5 件について詳しく調査し、開発者とコンパイラーとの「対立」を最も引き起こしがちな Rust プログラミング言語の側面についてより一般的な考察を行います。
一般的なエラー第 5 位: E0433(An undeclared crate, module, or type was used; 未宣言のクレート、モジュール、または型が使用されました)
このエラーは前回のパートで説明した E0432(インポートが解決されませんでした)と同様のものです。 唯一の違いは、問題のコンポーネントがあるパスが use
節を使用せずに直接名前に使用されていることです。 RustRover ユーザーの 17.5% がこのエラーに遭遇しています。 たとえば、以下のコードではコンパイラーはユーザーの意図(クレートまたはモジュール)を理解できずにエラーを吐いています。
Rust コンパイラーが広く称賛されているのは、このようなエラーメッセージ出力を行うためです。 メッセージを読まなくても修正すべき内容が明確に分かるのです。ここでは名前を検証するか、必要に応じて必須の依存関係を Cargo.toml に追加するか、適切なインポートを導入するかです。
RustRover も役立ちます。 インポートされる名前の選択肢を提供してくれます。
コーディングしながらコード補完を使用することで、このエラーをまとめて回避することができます。
一般的なエラー第 4 位: E0425(An unresolved name was used; 未解決の名前が使用されました)
さらに別の「未解決」エラーを伴う不明な現象を詳しく見てみましょう。 RustRover ユーザーの 20.5% が構文的にクレートまたはモジュール名でない未解決の名前が使用されているという状況に遭遇しています。 このエラーが発生した場合、以下のポップアップが表示されます。
当然ではありますが、実際の発生時には上の画像ほど分かりやすくはない可能性があります。
残念ながら、輝かしい Rust コンパイラーであってもこのエラーを解決できません。 ここでは名前を変更する、定義を指定する、適切なインポートを導入するといったいくつかの対処方法があります。
一般的なエラー第 3 位: E0599(A method is used on a type which doesn’t implement it; メソッドがそのメソッドを実装していない型に対して使用されています)
ネタバレになってしまいますが、上位 3 位のエラーはすべて型チェックエラーとなっており、 すべて誤った型の使用に関するものとなっています。 ここで取り上げているエラーは、RustRover ユーザーの 27.5% があるメソッドをそれを実装していない型の値に対して呼び出そうとした際に遭遇しているものです。 以下の例を見てみましょう。
fn main() { let numbers: Vec = vec![4, 11, 15, 12]; let sum: usize = numbers.sum(); println!("Answer = {sum}"); }
Vec
に sum
メソッドがないことを除けば、正しいように見えます。 具体的には、以下のようにエラーメッセージが表示されます。
error[E0599]: `Vec` is not an iterator
この場合は iter
メソッドを先に呼び出した後に結果のイテレーターに sum
を使用することで修正できます。
ちなみに、RustRover にはこのようなエラーを回避する機能があります。 「チェーン補完」と呼ばれる機能です。 このメリットを実際に確認するため、以下の例を見てみましょう。
補完候補が選択されると、iter
メソッドと sum
メソッドの両方が呼び出しチェーンに追加され、即時にコードが修正されます。
一般的にはコード補完を使用することで E0599 メッセージの発生を回避できます。 必要なメソッド呼び出しの候補が表示されない場合、実際には不正な呼び出し先の型が問題である可能性があります。
もう 1 つお伝えしておくべきこのエラーの側面があります。ユーザーは多くのコンテキストで自身の型宣言を制御できるということです。 たとえば、メソッドを呼び出した後に Alt-Enter(⌥Option–Return)を押して提案されるアクションを実行し、RustRover にそのメソッド実装のスキャフォールドを求める方法があります。
このアクションの結果、RustRover が型に関するすべてのコンテキスト情報を考慮してメソッド実装テンプレートを生成します。
impl Info { pub(crate) fn summary(&self) -> String { todo!() } }
このコードを実行するとランタイムエラーが発生しますが、少なくともコンパイラーがエラーを吐くことはなくなります。
一般的なエラー第 2 位: E0308(Expected type did not match the received type; 必要な型と受け取った型が一致しませんでした)
型 B
の値が必要なコンテキストで型 A
の式を使用している状況を想像してみましょう。 ここで言う「コンテキスト」は、呼び出しサイト、変数宣言、制御フローステートメント/式などの関数のパラメーターを指します。 このエラーに対処したことのある方は RustRover ユーザーの 30% と同じ経験を共有しています。
この場合、理論的には値を調整するかコンテキストを調整するかの 2 つの選択肢があります。 値の調整には型のキャスト、参照/逆参照、値に対する変換メソッドの呼び出し、または値から別の要素への置換が伴う場合があります。 コンテキストの調整は必ずしも可能ではありませんが、必要な型を受け取る型に合わせて変更できる場合もあります。
RustRover は以下のようなクイックフィックスを提供することで、どちらの処理にも対応できるようにしています。
リスト内の 2 つ目の候補に注目してください。この場合、RustRover はコンパイラーに適した非常に高度な値の変換を提案しています。 最初の候補を適用した場合はコンテキスト、つまりは関数定義が代わりに修正されます。
一般的なエラー第 1 位: E0277(You tried to use a type which doesn’t implement some trait in a place which expected that trait; トレイトが必要な場所でトレイトを実装しない型を使用しようとしました)
ようやく最後のエラーにたどり着きました! RustRover で遭遇する最も一般的な Rust コンパイラーエラーは E0277 です。 RustRover ユーザーの 32% が、この型とトレイトに関する開発者にとっては悪夢となる経験をしています。 公式の説明には例によって十分な例と適用可能な修正の説明が記載されています。 ここでは代わりに RustRover の動作に焦点を当ててみましょう。
以下はこの種のエラーを解決する RustRover の機能で、私のお気に入りです。
コンパイラーは以下のようなエラーを吐きます。
error[E0277]: `Info` doesn't implement `std::fmt::Display`
もちろん、Display
トレイトの実装は選択肢の 1 つであり、RustRover は実装を喜んでスキャフォールドしてくれます。 ただし、この場合は以下の 2 つのステップを同時に伴う最初の候補を適用したいと思います。
Debug
トレイトの実装を派生させる。- 書式設定文字列を変更して
Debug
トレイトの書式設定機能を呼び出す。
残念ながら、他の多くの場合には RustRover がこのエラーを自動的に検出してハイライトすることはできません。 前のパートで述べたように、RustRover の型チェック機能はまだそれほど強力でなく、開発途中であるためです。 E0277 は最も一般的に発生するエラーメッセージとして確実に注目されています。
Rust エラーについての一般的な考察
より大きな視点から考察してみましょう。 弊社は先週、Rust プログラミング言語のどの側面がコードエラーの主な発生源であるかについて X(旧 Twitter)コミュニティに尋ねました。 以下のような回答が得られました。
コミュニティの回答と弊社のデータから読み取れる内容を比較するため、最も一般的な Rust コンパイラーエラーの上位 25 件を確認し、これらを大まかに 5 つのカテゴリに分類しました。
- Types and traits(型とトレイト)
- Ownership and lifetimes(所有権とライフタイム)
- Macros(マクロ)
- Unresolved names or nonexistent elements(未解決の名前または存在しない要素)
- Other(その他)
結果は以下のとおりです(エラーコード番号昇順):
エラーコード | 説明 | カテゴリ |
E0061 | An invalid number of arguments was passed when calling a function(関数を呼び出す際に無効な数の引数が渡されました) | その他 |
E0063 | A struct’s or struct-like enum variant’s field was not provided.(構造体のフィールドまたは構造体のような列挙型バリアントのフィールドが指定されませんでした) | 未解決/存在しない |
E0106 | このエラーは型のライフタイムが欠落していることを示します。 | 所有権とライフタイム |
E0107 | An incorrect number of generic arguments was provided.(誤った数のジェネリクス引数が指定されました。) | 型とトレイト |
E0277 | You tried to use a type which doesn’t implement some trait in a place which(トレイトが必要な場所でトレイトを実装しない型を使用しようとしました) | 型とトレイト |
E0282 | The compiler could not infer a type and asked for a type annotation(コンパイラーが型を推論できず、型アノテーションを要求しました) | 型とトレイト |
E0283 | An implementation cannot be chosen unambiguously because of lack of information.(情報不足のため、実装を明確に選択できません。) | 型とトレイト |
E0308 | Expected type did not match the received type.(必要な型と受け取った型が一致しませんでした。) | 型とトレイト |
E0369 | A binary operation was attempted on a type which doesn’t support it.(バイナリ演算がサポートされていない型に対してバイナリ演算が試行されました。) | 型とトレイト |
E0382 | A variable was used after its contents have been moved elsewhere(変数の内容が他の場所に移された後にその変数が使用されました) | 所有権とライフタイム |
E0412 | A used type name is not in scope.(使用されている型名がスコープ内にありません。) | 未解決/存在しない |
E0423 | An identifier was used like a function name or a value was expected and the identifier exists but it belongs to a different namespace.(識別子が関数名のように使用されていたか、値が必要で識別子が存在していても別の名前空間に属しています。) | 未解決/存在しない |
E0425 | An unresolved name was used.(未解決の名前が使用されました。) | 未解決/存在しない |
E0432 | An import was unresolved.(インポートが解決されませんでした。) | 未解決/存在しない |
E0433 | An undeclared crate, module, or type was used.(未宣言のクレート、モジュール、または型が使用されました。) | 未解決/存在しない |
E0502 | A variable already borrowed as immutable was borrowed as mutable.(イミュータブルとしてすでに借用されている変数がミュータブルとして借用されました。) | 所有権とライフタイム |
E0507 | A borrowed value was moved out.(借用された値が移動されました。) | 所有権とライフタイム |
E0515 | A reference to a local variable was returned.(ローカル変数への参照が返されました。) | 所有権とライフタイム |
E0596 | このエラーはイミュータブルの変数をミュータブルで借用しようとしたために発生しています。 | 所有権とライフタイム |
E0597 | このエラーは値が借用されている際に削除されたことが原因で発生します。 | 所有権とライフタイム |
E0599 | このエラーはあるメソッドがそのメソッドを実装していない型に対して使用されている場合に発生します。 | 型とトレイト |
E0609 | Attempted to access a nonexistent field in a struct.(構造体に存在しないフィールドにアクセスしようとしました。) | 未解決/存在しない |
E0614 | Attempted to dereference a variable which cannot be dereferenced.(逆参照できない変数を逆参照しようとしました。) | その他 |
E0658 | An unstable feature was used.(不安定な機能が使用されました。) | その他 |
E0716 | A temporary value is being dropped while a borrow is still in active use.(一時的な値がまだ借用中に破棄されています。) | 所有権とライフタイム |
残念ながら、弊社が収集したデータではマクロについては詳細には分かりません。 マクロ展開の問題を確実に検出することも、正常に展開されたマクロに起因するその他のエラーも特定できません。 これは、弊社が収集するデータにより細かい分類を導入する必要があることを示唆していると思われます。
マクロを無視して上位 25 件の一般的なエラーを見る限り、開発者を大いに苦しめる Rust の要素はないように見えます。そのため、私たちは例外なく Rust のあらゆる部分を気に入っているのです。
- 型とトレイト – エラー 7 件
- 所有権とライフタイム – エラー 8 件
- 未解決の名前または存在しない要素 – エラー 7 件
- その他 – エラー 3 件
まとめ
連載のこのパートでは、RustRover で遭遇する最も一般的な Rust コンパイラーエラーのうち上位 5 件について焦点を当てました。 このうち 3 件のエラーは型とトレイトに関連するものであり、このカテゴリが Rust コードを正す上である程度重要なものであることがうかがえます。 25 件の最も一般的なエラーに関するより一般的なデータを考察して結論を出し、頻繫に遭遇するエラーには Rust の他の側面も関与していることがわかりました。
オリジナル(英語)ブログ投稿記事の作者: