コンテンツにスキップ

Rustのエラー E0284: type annotations needed(parse()の変換先の型)

E0284 が言っていることは、ほぼいつも同じです。.parse() を呼んだけれど、文字列を何に変換すればいいのか Rust にはわからない、という意味です。cannot satisfy `<_ as FromStr>::Err == _` という注記は怖そうに見えますが、直し方はたいてい : u32 や ::<u32> のような数文字だけです。このページでは、まず直し方を見せてから、その注記を読み解いて、怖くなくなるようにします。このシリーズのほかのエラーは、Rustのコンパイルエラー解説を見てください。

error[E0284]: type annotations needed
--> src/main.rs:2:9
|
2 | let age = "42".parse().unwrap();
| ^^^ ----- type must be known at this point
|
= note: cannot satisfy `<_ as FromStr>::Err == _`
help: consider giving `age` an explicit type
|
2 | let age: /* Type */ = "42".parse().unwrap();
| ++++++++++++

「type annotations needed」は「型注釈が必要」という意味です。help は「age に型を明示してはどうか」と提案しています。

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

fn main() {
let age = "42".parse().unwrap();
println!("来年は{}歳ですね", age + 1);
}

.unwrap() の代わりに .expect(...) を使ったときや、match "42".parse() { ... } と書いたとき、クロージャの中で .parse() を使ったときにも、同じ注記が出ます。.parse() の結果を変数にしまっていないときは、rustc は parse そのものを指して、parse::</* Type */>() を提案します。

記号のほうはかんたんです。

  1. age の下の ^^^: 型がわからない変数です。
  2. parse の下の -----: 「type must be known at this point」(ここで型がわかっていなければならない)。Rust が行き詰まった場所です。
  3. help: 「consider giving age an explicit type」(age に型を明示してはどうか)。/* Type */ を u32 のような本当の型に置き換えます。

次は注記を、部分ごとに見ていきましょう。コンパイラのメッセージでは、_ は「まだわからない型」という意味です。

部分 わかりやすく言うと
<_ as FromStr> 「変換先の型(まだわからない)」
::Err 「……と、それに付いてくるエラーの型」
== _ 「が、これまたわからない別の型と等しくなければならない」

つまり注記全体では、*「何に変換するのかわからないので、parse がどんなエラーを返しうるのかさえ決められない」*と言っているのです。

翻訳家に手紙を渡して「これを翻訳して」とだけ頼むようなものです。フランス語に?日本語に?それを言うまで翻訳家は始められませんし、どんな間違いが起こりうるのかさえ答えられません。

parse のシグネチャを、少しだけ簡単にすると次のようになります。

fn parse<F: FromStr>(&self) -> Result<F, F::Err>

F は返してほしい型で、F::Err はその型のエラーの型です。変換先の型ごとに、それぞれのエラーの型があります。i32 への変換は ParseIntError で、f64 は ParseFloatError で、bool は ParseBoolError で失敗することがあります。parse が何を返すのかを知るだけでも、Rust には F が必要なのです。

この選択は、実行時の動きも変えます。"42" なら u8 でも i32 でも f64 でも問題なく変換できます。でも "300" は u8 では失敗します(大きすぎる)し、"4.5" は i32 では失敗します(整数ではない)。Rust は推測しません。推測が外れると、特定の入力のときだけ表に出るバグになってしまうからです。FromStr のしくみと、自分の型に実装する方法は、型変換のレッスンで紹介しています。

Rust が自分で型を推論するしくみは、E0282 のページで説明しています。「type annotations needed」に3つの違うコード(E0282、E0283、E0284)がある理由は、E0283 のページで説明しています。手短に言うと、E0284 は、行き詰まったルールが、わからない型に付属する型(ここでは F::Err)についてのものであるときのバリエーションです。私たちにとっての直し方は同じで、型を書くことです。

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

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

ほとんどの場合はこれです。Rust の本(The Rust Programming Language)が使っている書き方で、文字列に何が入っているはずなのかを伝える説明にもなります。

fn main() {
let age: u32 = "42".parse().unwrap();
let price: f64 = "4.99".parse().unwrap();
let port: u16 = "8080".parse().expect("ポートは数値でなければなりません");
println!("来年は{}歳ですね", age + 1);
println!("コーヒー2杯で{:.2}", price * 2.0);
println!("{port}番ポートで待ち受けています");
}

実行結果:

来年は43歳ですね
コーヒー2杯で9.98
8080番ポートで待ち受けています
▶ 実際に試してみよう

文字列の意味に合った型を選びましょう。ポートなら u16、パーセントなら u8、値段なら f64 です。小さすぎる型を選ぶと、正しい入力までエラーになってしまうので、迷ったら i64 や u32 のようなゆとりのある型を選びましょう。

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

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

型注釈を付ける変数がないときは、これを選びます。クロージャの中、イテレータのチェーンの途中、... * 2 のような式の中などです。

fn main() {
let line = "3 7 5";
let total = line
.split(' ')
.map(|s| s.parse::<u32>().unwrap())
.sum::<u32>();
let doubled = "21".parse::<i32>().unwrap() * 2;
println!("合計: {total}");
println!("2倍: {doubled}");
}

実行結果:

合計: 15
2倍: 42
▶ 実際に試してみよう

ターボフィッシュは parse そのものに付けましょう。チェーンの中では、最後の結果に型注釈を付けるだけでは足りないことがよくあります。下の「よく出会う場面」の最初の項目を見てください。

直し方3:型を書いて、エラーをきちんと処理する

Section titled “直し方3:型を書いて、エラーをきちんと処理する”

ファイル、コマンドライン引数、Web フォームのような本物の入力には、これを選びます。そういう入力では、.unwrap() はおかしな文字列が来たときにプログラムをクラッシュさせてしまいます。parse に型を書いて、Result を match しましょう。型によって、どの入力が成功するかが変わることに注目してください。

fn main() {
for input in ["42", "300", "abc"] {
match input.parse::<u8>() {
Ok(n) => println!("{input}: {n} に変換できました"),
Err(e) => println!("{input}: {e}"),
}
}
let rate: Result<f64, _> = "0.25".parse();
println!("{:?}", rate);
}

実行結果:

42: 42 に変換できました
300: number too large to fit in target type
abc: invalid digit found in string
Ok(0.25)
▶ 実際に試してみよう

英語のメッセージは Rust 自身のエラーメッセージで、「number too large to fit in target type」は「数が大きすぎて変換先の型に収まらない」、「invalid digit found in string」は「文字列に数字として使えない文字がある」という意味です。

Result<f64, _> では、値の型だけを書いています。_ のところは Rust がエラーの型(ParseFloatError)を埋めてくれます。これこそ、あの注記が聞いていた部分です。詳しくはResultを見てください。

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

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

変換した値を、型の決まっている構造体のフィールドや関数の引数に渡すときは、これを選びます。Rust は関数全体を読むので、あとでの使い方が答えになります。

struct Config {
port: u16,
workers: u8,
}
fn greet(age: u32) {
println!("ようこそ!あなたは{age}歳ですね。");
}
fn main() {
let port = "8080".parse().unwrap(); // Config から u16 に決まる
let workers = "4".parse().unwrap(); // Config から u8 に決まる
let config = Config { port, workers };
println!("ポート{}、ワーカー{}個", config.port, config.workers);
let age = "42".parse().unwrap(); // greet から u32 に決まる
greet(age);
}

実行結果:

ポート8080、ワーカー4個
ようこそ!あなたは42歳ですね。
▶ 実際に試してみよう

どんな使い方でも答えになるわけではありません。だからこれは直し方4なのです。age + 1 のような計算や、age >= limit のような比較は、limit が u32 でも E0284 のままです。演算子はいろいろな型の組み合わせで使えるので、ひとつの型に決められないのです。また、age.pow(2) のようにメソッドを呼ぶと、エラーは E0282 に変わります。迷ったら直し方1を使いましょう。

map の中で parse して、sum() する。 let total: u32 = line.split(' ').map(|s| s.parse().unwrap()).sum(); は、total に型があるのに E0284 のままです。u32 は u32 の値からも &u32 の値からも合計できるので、この型注釈では各要素の型が決まらないのです。直し方:s.parse::<u32>()。(Vec<i32> に collect するなら、うまくいきます。Vec<i32> には i32 しか入らないからです。)

parse の結果を Result に collect する。 let nums: Result<Vec<_>, _> = text.split(' ').map(|x| x.parse()).collect(); は E0284 になります。Vec<_> の _ のせいで、要素の型が決まらないままだからです。直し方:Result<Vec<i32>, _>。これは便利なパターンで、最初のおかしな要素で止まって、そのエラーを返してくれます。

ユーザーの入力を読むとき。 数当てゲームやコマンドラインツールでの let guess = line.trim().parse().unwrap(); です。直し方:Rust の本と同じように let guess: u32 = ... と書きます。

? を使うと、もっと不思議なエラーになる。 let port = text.parse()?; は E0284 になりません。エディション 2021 では、Rust は型を () にしてしまい、E0277 の the trait bound `(): FromStr` is not satisfied(() は FromStr を実装していない)を報告します(エディション 2024 では () の代わりに ! と表示されます)。直し方は同じで、let port: u16 = text.parse()?; と書きます。

  • E0282:型注釈が必要。手がかりがまったくないとき(または age.pow(2) のようなメソッド呼び出しのために型が必要なとき)に出ます。
  • E0283:型注釈が必要。.sum()、.collect()、.into() のあとで、当てはまる型がたくさんあるときに出ます。コードが3つに分かれている理由もここで説明しています。
  • E0790:FromStr::from_str("42") や Default::default() を、どの型なのかを書かずに呼んだ。
  • E0277:型を書かずに .parse()? としたときに出るエラーです。選んだ型が文字列から変換できないときにも出ます。
  • E0599:メソッドが見つからない。たとえば u32 の変数のように、文字列ではないものに .parse() を呼んだとき(その場合は u8::try_from(n) で変換しましょう)。
  • コンパイルエラーの一覧:エラー解説ページの全リストです。

理解度チェック

1. cannot satisfy <_ as FromStr>::Err == _ をわかりやすく言うと、どういう意味ですか?

2. let port = "8080".parse().unwrap(); を直すのはどの行ですか?

3. let total: u32 = line.split(' ').map(|s| s.parse().unwrap()).sum(); はまだ E0284 になります。型はどこに書けばいいですか?

スコア: 0 / 3