Rustのエラー E0283: type annotations needed(sum・collect・intoの型注釈)
E0283 は、Rust には値に何ができなければならないかはわかっているけれど、それができる型がたくさんあって、Rust が勝手に1つを選ぶことはしない、という意味のエラーです。たいていは .sum()、.collect()、.into() のすぐあとに出会います。直すのがいちばん簡単なエラーの1つで、ほしい型を書けばそれで終わりです。このシリーズのほかのエラーは、Rustのコンパイルエラー解説を見てください。
エラーメッセージ
Section titled “エラーメッセージ”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 othersnote: 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>` です。
エラーの意味
Section titled “エラーの意味”メッセージを上から順に読んでいきましょう。
totalの下の^^^^^: Rust が型を決められなかった変数です。sumの下の---: 「type must be known at this point」(この時点で型がわかっていなければならない)。sumはいろいろな型を返せるので、Rust はここで答えを知る必要があります。- note:
the type must implement `Sum<&i32>`。Rust が見つけたルールです。「totalは、&i32の値を足し合わせて作れる型でなければならない」ということです。 - help のリスト: 「何かの合計」になれる型の一覧です。すべての数値型、
Duration、さらにはOptionやResultまで、たくさんあります。Rust は8つだけ見せて、「and 88 others」(ほかに88個)と書いています。複数の impl がこの境界を満たす(multiple impls satisfy this bound)と言われるのは、このことです。ルールを満たすimplを持つ型が、1つではないのです。
カフェで「何か飲み物を」と頼んだところを想像してください。ちゃんとしたルール(飲めるもの)ではありますが、紅茶もジュースも水も当てはまります。あなたが選ぶまで、店員さんは何も持ってこられません。E0283 は、Rust がトレイを持ったまま、あなたの答えを待っている状態です。
Rust がこうする理由
Section titled “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 42300 は u8 に収まる? falseString::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つの型に決まる使い方でなければなりません。
よく出会う場面
Section titled “よく出会う場面”.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(); と書きます。
関連するエラー
Section titled “関連するエラー”- E0282:型注釈が必要。Rust に型の手がかりがまったくないとき(またはメソッドを呼ぶために型が必要なとき)に出ます。
- E0284:
cannot satisfy `<_ as FromStr>::Err == _`付きの「型注釈が必要」。ほとんどいつも.parse()から出ます。 - E0790:どの型かを言わずに、
Default::default()(やほかのトレイトの関数)を呼んだ。 - E0277:型は書いたけれど、その型はそういうふうには作れない。たとえば
let n: Vec<i32> = "1 2".split(' ').collect();です。 - E0107:ジェネリック引数の数が違う。
.into::<String>()と書くと、このエラーになります。 - コンパイルエラーの一覧:エラー解説ページの全リストです。
理解度チェック
Section titled “理解度チェック”理解度チェック
1. E0283 の注記 the type must implement Sum<&i32> は、何を伝えていますか?
2. let name = "Ferris".into(); を直せるのはどれですか?
3. 同じ単語を Vec<&str> と HashSet<&str> に collect すると、結果が違うのはなぜですか?
スコア: 0 / 3
さらに詳しく
Section titled “さらに詳しく”- E0283 — Rust error codes index(英語):このエラーの公式の説明です。逆順にした
chars()はVec<char>とStringのどちらになるべきか、という定番の例が載っています。 - 一連の要素をイテレータで処理する — The Rust Programming Language 日本語版:
sumやcollectがイテレータを消費するしくみを、型注釈付きの例で説明しています。 Iterator::collect— 標準ライブラリのドキュメント(英語):型注釈を付ける書き方と、ターボフィッシュを使う書き方を並べて紹介しています。Sum— 標準ライブラリのドキュメント(英語):.sum()の裏にあるトレイトと、それを実装している型の全リストです。Into— 標準ライブラリのドキュメント(英語):Fromを実装して.into()を呼ぶ理由と、変換先の型がどう決まるかがわかります。