コンテンツにスキップ

Rustのエラー E0308: mismatched types(型の不一致)

E0308 は、Rust がある型の値を期待していたのに、違う型の値が渡されたという意味のエラーです。Rust でいちばんよく目にするエラーかもしれません。でも安心してください。メッセージにはほぼ必ず2つの型が書かれているので、たいていすぐに直せます。(ほかのエラーを探しているなら、Rustのコンパイルエラー解説を見てください。)

error[E0308]: mismatched types
--> src/main.rs:6:29
|
6 | let user = User { name: "Ferris" };
| ^^^^^^^^ expected `String`, found `&str`
|
help: try using a conversion method
|
6 | let user = User { name: "Ferris".to_string() };
| ++++++++++++

「mismatched types」は「型が一致しない」という意味です。String を期待していたのに &str が見つかった、と言っていて、help は「変換メソッドを使ってみては」と .to_string() を提案しています。

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

struct User {
name: String,
}
fn main() {
let user = User { name: "Ferris" };
println!("{}", user.name);
}

E0308 のメッセージは、いつも同じ形をしています。expected A, found B(A を期待したが、B が見つかった)です。

  • expected(期待された型)は、その場所で Rust が必要としている型です。この型はどこか別の場所から決まります。型注釈、関数の引数や戻り値の型、構造体のフィールド、あるいは if/match のほかの分岐などです。
  • found(見つかった型)は、あなたが実際にそこに書いた値の型です。

この例では、name フィールドが String として宣言されているので、Rust は String を期待します。ところが "Ferris" は文字列リテラルで、型は &str です。この2つは関係の深い型ですが、同じ型ではありません。

help: の行もぜひ見てください。E0308 では、コンパイラが上の .to_string() のように、そのまま使える直し方を教えてくれることがよくあります。

Rust は、型と型の間の変換を黙って行うことがありません。言語によっては、数値がいつの間にか文字列になったり、大きな数が小さな型に収まるようにこっそり切り詰められたりします。こうした見えない変換は、データの消失や間違った計算結果、本当の原因から遠く離れた場所でのクラッシュといった、本物のバグを生みます。

そこで Rust は、すべての変換をコードの上に見える形で書かせます。少しだけタイプ量は増えますが、あとでコードを読んだときに、どこで値の型が変わるのか、その変換が失敗しうるのかがひと目でわかります。型そのものについてはデータ型を見てください。

それぞれの直し方の最初に、エラーの形を載せています。自分のエラーに合うものから読んでください。

直し方1:末尾のセミコロンを消す

Section titled “直し方1:末尾のセミコロンを消す”
error[E0308]: mismatched types
--> src/main.rs:1:46
|
1 | fn total_price(price: u32, quantity: u32) -> u32 {
| ----------- ^^^ expected `u32`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression
2 | price * quantity;
| - help: remove this semicolon to return this value

コンパイラは「本体に末尾の式も return もないので、暗黙に () を返している」と言い、help で「この値を返すには、このセミコロンを消して」と教えてくれています。

found () と出たら、関数の最後の行を確認しましょう。Rust では、ブロックの最後にあるセミコロンなしの式が、そのブロックの戻り値になります。; を付けると文になってしまうので、関数は代わりに ()(「何もない」を表す型)を返します。セミコロンを消しましょう。

fn total_price(price: u32, quantity: u32) -> u32 {
price * quantity // セミコロンなし:この値が返される
}
fn main() {
println!("合計: {}", total_price(250, 3));
}

実行結果:

合計: 750
▶ 実際に試してみよう

if のブロックや match の腕の中でも、同じことが起こります。詳しくは関数を見てください。

直し方2:String と &str を変換する

Section titled “直し方2:String と &str を変換する”

String は、自分で所有していて伸ばすこともできる文字列です。&str は、どこかにあるテキストを借りて見ているだけのものです。くわしくは文字列を見てください。変換はどちら向きでも短く書けます。

  • &str から String へ:.to_string() か String::from(...)。
  • String から &str へ:& で借用する(または .as_str() を呼ぶ)。

2つ目のケースは expected &str, found String という形で出てきます。たいていは、&str を受け取る関数に String を渡したときです。

struct User {
name: String,
}
fn greet(name: &str) {
println!("こんにちは、{name}さん!");
}
fn main() {
// &str -> String:.to_string() か String::from()
let user = User { name: "Ferris".to_string() };
// String -> &str:& で借用する(または .as_str() を呼ぶ)
greet(&user.name);
greet(user.name.as_str());
let mut tags: Vec<String> = Vec::new();
tags.push(String::from("rust"));
println!("{tags:?}");
}

実行結果:

こんにちは、Ferrisさん!
こんにちは、Ferrisさん!
["rust"]
▶ 実際に試してみよう
error[E0308]: mismatched types
--> src/main.rs:3:22
|
3 | let count: i32 = names.len();
| --- ^^^^^^^^^^^ expected `i32`, found `usize`
| |
| expected due to this

i32 を期待していたのに usize が見つかった、というエラーです。「expected due to this」(これのせいで期待された)のラベルは、期待の元になった型注釈を指しています。

i32、i64、u32、usize はすべて別々の型で、Rust はこれらを混ぜてくれません。長さやインデックスは、いつも usize です。次のどれかで変換しましょう。

  • .into()(または i64::from(x)):i32 から i64 のように、データが失われることが絶対にない変換のとき。
  • i64::try_from(x)(または .try_into()):値が収まらないかもしれないとき。Result が返るので、失敗したときどうするかを自分で決められます。
  • as:手軽なキャストです。失敗することはありませんが、収まらない値は黙って切り詰められます。値が小さいとわかっているときだけ使いましょう。
fn average(total: i64, count: i64) -> i64 {
total / count
}
fn main() {
let scores: Vec<i32> = vec![80, 95, 70];
let total: i32 = scores.iter().sum();
// i32 -> i64 は必ず収まるので、.into() で十分
let total: i64 = total.into();
// usize -> i64 は収まらないかもしれないので、try_from で確かめる
let count = i64::try_from(scores.len()).unwrap();
println!("平均: {}", average(total, count));
// `as` でも動くが、収まらない値は黙って切り詰められる
let players = scores.len() as i32;
println!("プレイヤーは{players}人");
}

実行結果:

平均: 81
プレイヤーは3人
▶ 実際に試してみよう

いちばん簡単なのは、型注釈のほうを変えることだったりします。let count: usize = names.len(); のようにです。さらに詳しくは型キャストと型の変換のページで説明しています。

直し方4:Some で包む、または Option から取り出す

Section titled “直し方4:Some で包む、または Option から取り出す”
error[E0308]: mismatched types
--> src/main.rs:3:27
|
3 | let position: usize = names.iter().position(|n| *n == "Ben");
| ----- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `usize`, found `Option<usize>`
| |
| expected due to this

usize を期待していたのに Option<usize> が見つかった、というエラーです。

Option<usize> は usize ではありません。「usize があるかもしれないし、何もないかもしれない」という型です。この食い違いは、どちら向きにも起こります。

  • expected T, found Option<T>:「何もない」場合をきちんと扱う必要があります。match、if let、unwrap_or(既定値) などを使います。
  • expected Option<T>, found T:関数が Option を返すので、値を包みます。10 ではなく Some(10) と書きます。
fn find_discount(code: &str) -> Option<u32> {
if code == "SAVE10" {
return Some(10); // T -> Option<T>:Some で包む
}
None
}
fn main() {
let names = vec!["Ana", "Ben", "Cy"];
// Option<T> -> T:「見つからなかった」場合を扱う……
match names.iter().position(|n| *n == "Ben") {
Some(i) => println!("Benの位置: {i}"),
None => println!("Benはいません"),
}
// ……または unwrap_or で既定値を選ぶ
let discount: u32 = find_discount("HELLO").unwrap_or(0);
println!("割引: {discount}%");
}

実行結果:

Benの位置: 1
割引: 0%
▶ 実際に試してみよう

コンパイラが .expect("REASON") を提案してくることもあります。これでも動きますが、値が None のときにプログラムがクラッシュ(パニック)します。None が本当にありえないときだけ使いましょう。詳しくはOptionを見てください。

直し方5:if/else や match のすべての分岐を同じ型にそろえる

Section titled “直し方5:if/else や match のすべての分岐を同じ型にそろえる”
error[E0308]: `if` and `else` have incompatible types
--> src/main.rs:3:52
|
3 | let label = if stock > 0 { "in stock" } else { stock };
| ---------- ^^^^^ expected `&str`, found integer
| |
| expected because of this

「if と else の型に互換性がない」というエラーです。最初の分岐(expected because of this)のせいで &str が期待されたのに、else 側では整数が見つかった、と言っています。

if や match が値を生み出すとき、Rust はその値の型を1つに決める必要があります。そのため、すべての分岐が同じ型の値を返さなければなりません。最初の分岐で期待される型が決まり、エラーはそれと食い違う分岐を指します。match の場合は「match arms have incompatible types」(match の腕の型に互換性がない)というメッセージになります。食い違っている分岐を、ほかとそろえましょう。

fn status_text(code: u16) -> String {
match code {
200 => "OK".to_string(),
404 => "Not Found".to_string(),
_ => format!("不明なコード {code}"), // どの腕も String
}
}
fn main() {
let stock = 0;
let label = if stock > 0 { "在庫あり" } else { "売り切れ" }; // どちらも &str
println!("{label}");
println!("{}", status_text(404));
println!("{}", status_text(418));
}

実行結果:

売り切れ
Not Found
不明なコード 418
▶ 実際に試してみよう

ここでは1つの腕で String を作る format! が必要だったので、ほかの腕も String にそろえました。詳しくはif/elseとmatchを見てください。

  • ユーザーの入力を読むとき。 let age: u32 = input.trim(); と書くと「expected u32, found &str」になります。テキストは数値ではないからです。直し方: パースします。let age: u32 = input.trim().parse().unwrap();(またはエラーをきちんと扱います。Resultを見てください)。
  • Vec<String> にリテラルを push するとき。 tags.push("rust") は「expected String, found &str」になります。直し方: tags.push("rust".to_string()) と書くか、中身がすべてリテラルなら Vec<&str> にします。
  • 違う数値型どうしで計算するとき。 total が i64 で n が i32 のとき、total + n は E0308 になります(すぐ隣に E0277 が出ることもよくあります)。直し方: 先に片方を変換します。total + i64::from(n) のようにです。

理解度チェック

1. 関数に -> i32 と書いてあるのに、「expected i32, found ()」というエラーが出ました。いちばんありそうな原因はどれですか?

2. &str を受け取る関数があり、手元には let name = String::from("Ana"); があります。どう渡せばよいですか?

3. データが黙って失われることが絶対にない変換はどれですか?

スコア: 0 / 3