コンテンツにスキップ

Rustのエラー E0283: type annotations needed(sum・collect・intoの型注釈)

E0283 は、Rust には値に何ができなければならないかはわかっているけれど、それができる型がたくさんあって、Rust が勝手に1つを選ぶことはしない、という意味のエラーです。たいていは .sum()、.collect()、.into() のすぐあとに出会います。直すのがいちばん簡単なエラーの1つで、ほしい型を書けばそれで終わりです。このシリーズのほかのエラーは、Rustのコンパイルエラー解説を見てください。

error[E0283]: type annotations needed
--> src/main.rs:3:9
|
3 | let total = prices.iter().sum();
| ^^^^^ --- type must be known at this point
|
= note: the type must implement `Sum<&i32>`
= help: the following types implement trait `Sum<A>`:
`Duration` implements `Sum<&'a Duration>`
`Duration` implements `Sum`
`Option<T>` implements `Sum<Option<U>>`
`Result<T, E>` implements `Sum<Result<U, E>>`
`Saturating<u128>` implements `Sum<&'a Saturating<u128>>`
`Saturating<u128>` implements `Sum`
`Saturating<u16>` implements `Sum<&'a Saturating<u16>>`
`Saturating<u16>` implements `Sum`
and 88 others
note: required by a bound in `std::iter::Iterator::sum`
help: consider giving `total` an explicit type
|
3 | let total: /* Type */ = prices.iter().sum();
| ++++++++++++

「type annotations needed」は「型注釈が必要」という意味です。note は「その型は Sum<&i32> を実装していなければならない」、1つ目の help は「Sum<A> を実装している型は次のとおり」と言って候補を並べています。最後の help は「total に明示的な型を付けてはどうか」という提案です。

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

fn main() {
let prices = vec![250, 120, 99]; // 値段(セント単位)
let total = prices.iter().sum();
println!("合計: {total}セント");
}

.collect() や .into() でも、注記が違うだけで同じエラーになります。let words = text.split(' ').collect(); なら注記は the type must implement `FromIterator<&str>`(その型は FromIterator<&str> を実装していなければならない)、let name = "Ferris".into(); なら the type must implement `From<&str>` です。

メッセージを上から順に読んでいきましょう。

  1. total の下の ^^^^^: Rust が型を決められなかった変数です。
  2. sum の下の ---: 「type must be known at this point」(この時点で型がわかっていなければならない)。sum はいろいろな型を返せるので、Rust はここで答えを知る必要があります。
  3. note: the type must implement `Sum<&i32>`。Rust が見つけたルールです。「total は、&i32 の値を足し合わせて作れる型でなければならない」ということです。
  4. help のリスト: 「何かの合計」になれる型の一覧です。すべての数値型、Duration、さらには Option や Result まで、たくさんあります。Rust は8つだけ見せて、「and 88 others」(ほかに88個)と書いています。複数の impl がこの境界を満たす(multiple impls satisfy this bound)と言われるのは、このことです。ルールを満たす impl を持つ型が、1つではないのです。

カフェで「何か飲み物を」と頼んだところを想像してください。ちゃんとしたルール(飲めるもの)ではありますが、紅茶もジュースも水も当てはまります。あなたが選ぶまで、店員さんは何も持ってこられません。E0283 は、Rust がトレイを持ったまま、あなたの答えを待っている状態です。

sum、collect、into は、戻り値の型がジェネリックです。関数が何をするかは、あなたが求める型で決まります。トレイト(Sum、FromIterator、From)はただのルールです。だから、型を教えてくれるものが何もないと、Rust にはルールはあっても、答えが1つに決まりません。

そして、その答えは大事です。同じ単語を Vec<&str> に collect すれば重複が残りますが、HashSet<&str> に collect すれば重複は消えます。u8 に合計すると 255 であふれますが、u64 ならあふれません。もし Rust が勝手に型を選んだら、どこか別の場所のちょっとした変更で、プログラムの動きが黙って変わってしまうかもしれません。Rust は、一度だけ聞いておくほうを選ぶのです。

Rust が自分で型を割り出すしくみ(型推論、type inference)は E0282 のページで説明しているので、ここでは繰り返しません。これらのメソッドの裏にあるトレイトについては、トレイトとジェネリクスを見てください。

E0282、E0283、E0284:問題は1つ、コードは3つ

Section titled “E0282、E0283、E0284:問題は1つ、コードは3つ”

3つとも「type annotations needed」と言っていて、あなたにとっての意味は同じです。型を書きましょう。コードは、Rust が何をしようとして行き詰まったのかを表しているだけです。

  • E0282: Rust には型の手がかりがまったくないか、手がかりにたどり着く前に型が必要になった(たとえば .len() のようなメソッドを探すため)。
  • E0283(このページ): Rust にはトレイトのルールという手がかりがある(must implement `Sum<&i32>`)けれど、そのルールを満たす型がたくさんある。
  • E0284: ルールが、わからない型に属する別の型についてのもの。たとえば parse のエラー型です(cannot satisfy `<_ as FromStr>::Err == _`)。メインの型がわかるまで、Rust はチェックできません。

コードは、書いている途中で変わることもあります。let words = text.split(' ').collect(); のあとに println!("{:?}", words) があれば E0283 です。次の行を words.len() に変えると E0282 になります。メソッドを探すために型が必要になったからです。下の直し方は、3つとも使えます。

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

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

ほとんどの場合はこれを選びます。変数名のあとに : 型 を書きます。短いですし、何を作っているのかが次に読む人にもはっきりわかります。

use std::collections::HashSet;
fn main() {
let prices = vec![250, 120, 99];
let total: u32 = prices.iter().sum();
let tags = "rust,web,rust,cli";
let list: Vec<&str> = tags.split(',').collect();
let unique: HashSet<&str> = tags.split(',').collect();
println!("合計: {total}セント");
println!("{:?}", list);
println!("重複のないタグ: {}個", unique.len());
}

実行結果:

合計: 469セント
["rust", "web", "rust", "cli"]
重複のないタグ: 3個
▶ 実際に試してみよう

同じ collect() なのに、結果は2通りです。書いた型が、本当に指示になっているのです。中身の型は let list: Vec<_> = ... のように _ と書いて、Rust に任せることもできます。

直し方2:sum や collect にターボフィッシュを付ける

Section titled “直し方2:sum や collect にターボフィッシュを付ける”

println! の中、イテレータのチェーンの途中、関数の引数など、型注釈を付ける変数がないときは、これを選びます。::<型>(「ターボフィッシュ」)で、呼び出しに型を付けます。

fn main() {
let prices = vec![250, 120, 99];
println!("合計: {}セント", prices.iter().sum::<u32>());
let line = "tea,cake,soup";
let items = line.split(',').collect::<Vec<_>>();
let initials = line.split(',').map(|s| &s[..1]).collect::<String>();
println!("{:?}", items);
println!("頭文字: {initials}");
}

実行結果:

合計: 469セント
["tea", "cake", "soup"]
頭文字: tcs
▶ 実際に試してみよう

(&s[..1] は先頭の1バイトを切り出すので、ここでは単語を英語のままにしています。).product::<u64>() も同じように使えます。直し方1と直し方2はどちらも正しいので、読みやすいほうを選びましょう。

直し方3:.into() では変換先の型を書く(ターボフィッシュは使えない)

Section titled “直し方3:.into() では変換先の型を書く(ターボフィッシュは使えない)”

.into() と .try_into() には、これを選びます。"Ferris".into::<String>() と書きたくなりますが、これは別のエラーになります。型パラメータはメソッドではなく、Into トレイトのほうに付いているからです。

error[E0107]: method takes 0 generic arguments but 1 generic argument was supplied
--> src/main.rs:2:25
|
2 | let name = "Ferris".into::<String>();
| ^^^^ expected 0 generic arguments

「このメソッドはジェネリック引数を0個取るのに、1個渡された」という意味です。

代わりに、変数に型注釈を付けるか、変換先の型の From を直接呼びます。こちらも読みやすい書き方です。

fn main() {
let name: String = "Ferris".into();
let city = String::from("リスボン");
let small: u8 = 200;
let big: u64 = small.into();
let also_big = u64::from(small);
let level: i64 = 42;
let byte: u8 = level.try_into().unwrap();
let too_big = u8::try_from(300_i64);
println!("{name}は{city}に住んでいます");
println!("{big} {also_big} {byte}");
println!("300 は u8 に収まる? {}", too_big.is_ok());
}

実行結果:

Ferrisはリスボンに住んでいます
200 200 42
300 は u8 に収まる? false
▶ 実際に試してみよう

String::from(...) や u64::from(...) は、行の最初で変換先の型を言ってくれます。そのため、ほかに手がかりがないときは、.into() よりこちらを好む人がたくさんいます。型の変換も見てください。

直し方4:型が決まっている場所で値を使う

Section titled “直し方4:型が決まっている場所で値を使う”

関数の引数や構造体のフィールドのように、どのみち型の決まっている場所に値を渡すときは、これを選びます。Rust は関数全体を読むので、あとでそう使っていれば十分な手がかりになります。

fn print_receipt(total: u32, items: Vec<&str>) {
println!("{}品で{}セント", items.len(), total);
}
fn main() {
let prices = vec![250, 120, 99];
let total = prices.iter().sum(); // print_receipt のおかげで u32 になる
let items = "tea,cake,soup".split(',').collect(); // 同じ理由で Vec<&str> になる
print_receipt(total, items);
}

実行結果:

3品で469セント
▶ 実際に試してみよう

どんな使い方でも手がかりになるわけではありません。if prices.iter().sum() > 400 は、やはり E0283 になります。> はいろいろな型の組み合わせを比べられるからです。impl Display のようなジェネリックな引数に渡しても役に立ちません。Display な型はたくさんあるからです。ちょうど1つの型に決まる使い方でなければなりません。

.collect() をそのまま println! に入れるとき。 println!("{:?}", line.split(',').collect()); は E0283 になり、rustc 自身が直し方を提案してくれます。.collect::<Vec<_>>() です。

.try_into().unwrap()。 let small = big.try_into().unwrap(); は、the type must implement `TryFrom<i64>` という注記付きで E0283 になります。直し方:let small: u8 = big.try_into().unwrap(); か u8::try_from(big).unwrap() と書きます。

Default::default() をジェネリック関数に渡すとき。 fn show(value: impl Debug) に対して show(Default::default()) と呼ぶと E0283 になります。誰のデフォルト値がほしいのか、Rust にはわかりません。直し方:show(Settings::default()) や show(u32::default()) と書きます。単独の行で let s = Default::default(); と書くと、代わりに E0790 になります。Defaultも見てください。

自分で書いたジェネリック関数。 fn make<T: Default>() -> T のように、型パラメータが戻り値の型にしか出てこない場合、let v = make(); は E0283 になります。直し方:make::<Settings>() か let v: Settings = make(); と書きます。

  • E0282:型注釈が必要。Rust に型の手がかりがまったくないとき(またはメソッドを呼ぶために型が必要なとき)に出ます。
  • E0284:cannot satisfy `<_ as FromStr>::Err == _` 付きの「型注釈が必要」。ほとんどいつも .parse() から出ます。
  • E0790:どの型かを言わずに、Default::default()(やほかのトレイトの関数)を呼んだ。
  • E0277:型は書いたけれど、その型はそういうふうには作れない。たとえば let n: Vec<i32> = "1 2".split(' ').collect(); です。
  • E0107:ジェネリック引数の数が違う。.into::<String>() と書くと、このエラーになります。
  • コンパイルエラーの一覧:エラー解説ページの全リストです。

理解度チェック

1. E0283 の注記 the type must implement Sum<&i32> は、何を伝えていますか?

2. let name = "Ferris".into(); を直せるのはどれですか?

3. 同じ単語を Vec<&str> と HashSet<&str> に collect すると、結果が違うのはなぜですか?

スコア: 0 / 3