コンテンツにスキップ

Rustのエラー E0282: type annotations needed(型注釈が必要)

E0282 は、Rust が自分で型を割り出そうとしたけれど、手がかりが足りなかった、という意味のエラーです。あなたのロジックが間違っているわけではありません。コンパイラは、型を1つ教えてほしいだけです。たいていは数文字書き足せば直ります。このページでは、まず直し方を見せてから、Rust が型を推測するしくみ(型推論、type inference と呼びます)を説明します。そうすれば、どんなときに手を貸せばいいかがわかるようになります。このシリーズのほかのエラーは、Rustのコンパイルエラー解説を見てください。

error[E0282]: type annotations needed for `Vec<_>`
--> src/main.rs:2:9
|
2 | let mut scores = Vec::new(); // 何の Vec……?
| ^^^^^^^^^^ ---------- type must be known at this point
|
help: consider giving `scores` an explicit type, where the type for type parameter `T` is specified
|
2 | let mut scores: Vec<T> = Vec::new(); // 何の Vec……?
| ++++++++

「Vec<_> には型注釈が必要」という意味です。help は「scores に、型パラメータ T の型を指定した明示的な型を付けてはどうか」と提案しています。

このエラーが出るのは、次のプログラムです。

fn main() {
let mut scores = Vec::new(); // 何の Vec……?
println!("Scores recorded: {}", scores.len());
}

「type annotations needed」という文言は、ほかに3つのエラーコードでも出てきます。意味は同じで、このページの直し方はどれにも使えます。

  • E0283:.collect() や .sum() で、結果の型が書かれていない。結果になりうる型がたくさんあるので、rustc は the type must implement `FromIterator<&str>` や `Sum<&i32>` のような注記(その型は〜を実装していなければならない)を付けます。
  • E0284:"42".parse().unwrap() で、変換先の型が書かれていない。注記には cannot satisfy `<_ as FromStr>::Err == _` と出ます。ちょっと怖い見た目ですが、「何に parse するの?」と言っているだけです。
  • E0790:let settings = Default::default();。見出しは cannot call associated function on trait without specifying the corresponding `impl` type(対応する impl の型を指定せずに、トレイトの関連関数は呼べない)です。誰のデフォルト値がほしいのか、Rust にはわかりません。

どのコードになるかは、その行の書き方によって決まり、コードを書き足すと変わることもあります。たとえば let age = "42".parse().unwrap(); は E0284 になりますが、次の行で age.pow(2) を呼ぶと E0282 になります。番号は気にしなくて大丈夫です。rustc が指している変数を見ましょう。

2行目の下にある印を見てください。

  1. mut scores の下の ^^^^^^^^^^: Rust が型を割り出せなかった変数です。
  2. Vec::new() の下の ----------: 「type must be known at this point」(この時点で型がわかっていなければならない)。Vec::new() は空のリストを作りますが、何の空のリストでしょうか?数値?名前?正しい種類の置き場所を用意するために、Rust はそれを知る必要があります。
  3. help は let mut scores: Vec<T> を提案しています。この T はただの仮の名前です。Vec<u32> のように、実際の型に置き換えます。

郵便局に、「内容物」の欄が空のままの小包を持ってきたところを想像してください。誰かがそこに何か書くまで、窓口の人は送ることができません。自分で書いてもいいですし、中身を入れて見せてもかまいません。

Rust では、どの値にもちょうど1つの型があり、それはコンパイル時に決まります。ふだんは型を書かなくても、Rust がコードを読んで割り出してくれます。let count = 5; は i32、let name = String::from("Ferris"); は String です。これが型推論で、変数のレッスンでも紹介しています。

型推論は、1行だけでなく関数全体から手がかりを集めます。あとで scores.push(90) を呼んでいれば、それが手がかりになります。この Vec には数値が入るのだ、と。&[u32] を受け取る関数に scores を渡していれば、もっとよい手がかりになります。E0282 が出るのは、手がかりがまったくないとき、あるいは手がかりにたどり着く前に型が必要になったとき(たとえば、メソッドを探すため)です。

parse、collect、sum、Default::default、Vec::new、None のように、戻り値の型がジェネリックな関数もあります。これらはいろいろな型を作れるので、Rust が代わりに1つ選ぶことはできません。Rust は推測もしません。推測が外れると、プログラムの動きが黙って変わってしまうからです。1つの関数がいろいろな型を返せるしくみは、ジェネリクスを見てください。

直し方1:変数に型注釈を付ける

Section titled “直し方1:変数に型注釈を付ける”

ほとんどの場合はこれを選びます。変数名のあとに : 型 を付けます。短く書けて、次にコードを読む人への説明にもなります。

fn main() {
let scores: Vec<u32> = Vec::new();
let age: u32 = "42".parse().unwrap();
let words: Vec<&str> = "red green blue".split(' ').collect();
let winner: Option<String> = None;
println!("記録されたスコアの数: {}", scores.len());
println!("来年は{}歳になります", age + 1);
println!("{:?}", words);
println!("{:?}", winner);
}

実行結果:

記録されたスコアの数: 0
来年は43歳になります
["red", "green", "blue"]
None
▶ 実際に試してみよう

いつも型を全部書く必要はありません。let words: Vec<_> = ...collect(); は「Vec にしてね。中身の型はそっちで考えて」という意味です。_ の部分を Rust に埋めてもらうのです。型の書き方についてはデータ型で詳しく説明しています。

直し方2:ターボフィッシュ ::<> を使う

Section titled “直し方2:ターボフィッシュ ::<> を使う”

型注釈を付ける変数がないとき、たとえばイテレータのメソッドをつなげている途中では、これを選びます。::<型> と書いて、関数呼び出しに直接型を付けます。::<> が魚のように見えるので、「ターボフィッシュ」(turbofish)と呼ばれています。

fn main() {
let input = "4 15 7";
let numbers = input
.split(' ')
.map(|s| s.parse::<u32>().unwrap())
.collect::<Vec<_>>();
let total = numbers.iter().sum::<u32>();
println!("{:?}", numbers);
println!("合計: {total}");
}

実行結果:

[4, 15, 7]
合計: 26
▶ 実際に試してみよう

よく使うターボフィッシュは、parse::<i32>()、collect::<Vec<_>>()、sum::<i32>() の3つです。直し方1と直し方2はどちらも同じくらい正しく、let total: u32 = numbers.iter().sum(); でも同じことができます。読みやすいほうを選びましょう。

直し方3:値を使って、Rust に型を推論させる

Section titled “直し方3:値を使って、Rust に型を推論させる”

コードがまだ書きかけでエラーが出ているときは、これを選びます。空の Vec を作って、それに値を入れるコードはあとで書く、ということはよくあります。値を使えば Rust に手がかりが届くので、型注釈なしでエラーが消えます。

fn print_team(names: &[String]) {
for name in names {
println!("- {name}");
}
}
fn main() {
let mut team = Vec::new(); // 型はまだわからない……今のところは
team.push(String::from("Ferris")); // これで Rust にわかる:Vec<String>
team.push(String::from("Corro"));
print_team(&team);
}

実行結果:

- Ferris
- Corro
▶ 実際に試してみよう

順番が少しだけ大事です。手がかりは、Rust がメソッドを呼ぶために型を必要とする前に来なければなりません。let shout = |s| s.to_uppercase(); のようなクロージャは、次の行で shout("hi") を呼んでいてもエラーになります。.to_uppercase() を見つけるには、s が何なのかを先に知る必要があるからです。この場合は |s: &str| s.to_uppercase() と型を書きましょう。クロージャを見てください。

直し方4:None や Default::default() に型を書く

Section titled “直し方4:None や Default::default() に型を書く”

手がかりをまったく持たない値には、これを選びます。None は何を入れるはずだったのかを語りませんし、Default::default() は誰のデフォルト値がほしいのかを語りません。式の中に型を書き込みましょう。

#[derive(Debug, Default)]
struct Settings {
volume: u8,
muted: bool,
}
fn main() {
let fastest_lap = None::<f64>; // None の代わりに
let settings = Settings::default(); // Default::default() の代わりに
let backup: Settings = Default::default(); // 型注釈でもよい
println!("{:?}", fastest_lap);
println!("{:?}", settings);
println!("予備の音量: {}", backup.volume);
}

実行結果:

None
Settings { volume: 0, muted: false }
予備の音量: 0
▶ 実際に試してみよう

いちばんわかりやすいのは Settings::default() です。「Settings のデフォルト値」とそのまま読めます。None::<f64> も便利ですが、let fastest_lap: Option<f64> = None;(直し方1)のほうがよく使われます。Optionを見てください。

テキストを数値にパースするとき。 let guess = line.trim().parse().unwrap(); は E0284 になります(次に guess のメソッドを呼んでいれば E0282)。parse は i32 も u8 も f64 も、ほかにもたくさんの型を作れるからです。直し方:let guess: i32 = ... か .parse::<i32>() と書きます。あとで guess を i32 の変数と比べるだけでは、ここでは足りません。parse は呼び出したその場で型を必要とするからです。

.collect()。 Vec も String も HashSet も HashMap も作れる……ので、作る先の型がいつも必要です。直し方:let words: Vec<_> = ...collect(); か .collect::<Vec<_>>() と書きます。イテレータを見てください。

.sum() と .product()。 nums.iter().sum() は、the type must implement `Sum<&i32>` という注記付きの E0283 になります。i32 を合計すれば i32 になりそうですが、ほかの数値型も合計の結果になれるからです。直し方:let total: i32 = nums.iter().sum(); か .sum::<i32>() と書きます。

構造体のコンストラクタの中の空のコレクション。 まだ書きかけの new() 関数の中の let cache = HashMap::new(); です。直し方:完全な型を一度書く(HashMap<String, u32>)か、フィールドの型がすでに宣言されている構造体に入れて返します。それが Rust の使える手がかりになります。

  • E0308:型の不一致。let age: u32 = "42"; のように、型注釈と値が食い違うときに出ます。
  • E0277:トレイト境界を満たしていない。型は書いたけれど、その型はそういうふうには作れない、というときです。たとえば let n: Vec<i32> = "1 2".split(' ').collect();(これは Vec<&str> です。先に各要素をパースしましょう)。
  • E0599:メソッドが見つからない。たとえば let n: u32 と型注釈を付けたあとで、.trim() のような String のメソッドを呼んだときです。
  • E0004:パターンが網羅的でない。これも、rustc が直し方を書いてくれるエラーです。
  • コンパイルエラーの一覧:エラー解説ページの全リストです。

理解度チェック

1. let ids = Vec::new(); で E0282 が出ました。この Vec には u64 の値を入れるつもりです。どう直せばよいですか?

2. テキストはどう見ても数なのに、let n = "7".parse().unwrap(); に型が必要なのはなぜですか?

3. .collect::<Vec<_>>() の _ はどういう意味ですか?

スコア: 0 / 3