コンテンツにスキップ

Rustのエラー E0609: no field on type(存在しないフィールド)

E0609 は、value.something と書いたのに、value の型には something というフィールドがない、という意味のエラーです。たいていはタイプミスか、値が思っている型とちょっと違う(たとえば User ではなく Option<User> になっている)かのどちらかです。エラーメッセージには Rust から見えている正確な型が書かれていて、直すにはたいていそれだけで十分です。このシリーズのほかのエラーは、Rustのコンパイルエラー解説を見てください。

error[E0609]: no field `emial` on type `User`
--> src/main.rs:12:25
|
12 | println!("{}", user.emial); // ❌ タイプミス:フィールド名は `email`
| ^^^^^ unknown field
|
help: a field with a similar name exists
|
12 - println!("{}", user.emial); // ❌ タイプミス:フィールド名は `email`
12 + println!("{}", user.email); // ❌ タイプミス:フィールド名は `email`
|

「no field emial on type User」は「User 型に emial というフィールドはない」という意味です。help は「似た名前のフィールドがある」と教えてくれて、正しい書き方まで見せてくれています。

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

struct User {
name: String,
email: String,
}
fn main() {
let user = User {
name: String::from("Ferris"),
email: String::from("[email protected]"),
};
println!("{}", user.name);
println!("{}", user.emial); // ❌ タイプミス:フィールド名は `email`
}

エラーの見出しは2つの部分に分かれていて、どちらも大事です。

  • no field `emial`:ドットのあとに書いた名前で、Rust が見つけられなかったものです。
  • on type `User`:Rust が user の型だと考えているものです。

ですから、まず名前を確認し、次に型を確認しましょう。メッセージの型が思っていた構造体でなければ(たとえば Option<User>、&Option<User>、Vec<User>、&T)、名前は合っていて、問題は先に構造体を取り出す必要があることです。

お店で「2段目の棚にある青いの」と頼んだのに、そもそも違うお店にいた、というようなものです。頼み方は合っていて、いる場所が違うだけなのです。

Rust では、構造体のフィールドは、構造体を書いた時点で決まります。コンパイラはフィールドの一覧を全部知っていて、.field をすべてコンパイル時にチェックします。JavaScript のように「実行時に探して undefined になる」ことも、Python のように AttributeError になることもありません。間違ったフィールド名が、プログラムに紛れ込むことはないのです。詳しくは構造体を見てください。

Option<T> や Result<T, E> のような包み込む型は、別の型です。あなたの構造体はその中に入っていますが、包み自体はその構造体のフィールドを持っていません。Rust が中まで手を伸ばしてくれないのは、値がそもそもないかもしれないからです。値がないときにどうするかは、自分で書く必要があります。詳しくはOptionを見てください。

ジェネリクスのコードも、同じ厳しいルールに従います。fn greet<T>(x: &T) の T はどんな型にもなれます。フィールドを持たない i32 や bool もです。Rust はジェネリックな関数を、ありうるすべての T に対して一度にチェックするので、トレイト境界が約束していることしか使わせてくれません。そして、トレイトが約束できるのはメソッドだけで、フィールドは約束できません。詳しくはジェネリクスとトレイトを見てください。

メッセージの型が思っていたとおりの構造体なら、これを選びます。help: a field with a similar name exists(似た名前のフィールドがある)の行を探しましょう。たいていの場合、rustc はどのフィールドのつもりだったのかをちゃんとわかっています。

struct User {
name: String,
email: String,
}
fn main() {
let user = User {
name: String::from("Ferris"),
email: String::from("[email protected]"),
};
println!("{}", user.name);
println!("{}", user.email); // ✅ 本当のフィールド名
}

実行結果:

▶ 実際に試してみよう

ヒントが出ないときは、struct の定義に飛んで(たいていのエディタでは、型の上で Ctrl+クリックか F12)、そこから名前をコピーしましょう。フィールド名は大文字と小文字を区別します。userName と user_name は別のフィールドです。

直し方2:先に Option から構造体を取り出す

Section titled “直し方2:先に Option から構造体を取り出す”

メッセージの型が Option<User>、Option<&User>、&Option<User> のときは、これを選びます。何かが見つからないかもしれない関数や、.first()、.last()、.get()、.find() のようなメソッドを使うと、しょっちゅう起こります。

error[E0609]: no field `name` on type `Option<User>`
--> src/main.rs:11:25
|
11 | println!("{}", user.name);
| ^^^^ unknown field
|
help: one of the expressions' fields has a field of the same name
|
11 | println!("{}", user.unwrap().name);
| +++++++++

help は「式の中のフィールドの1つに、同じ名前のフィールドがある」と言って、.unwrap() を提案しています。このヒントどおりにすればコンパイルは通りますが、値が None だとプログラムがクラッシュします。それよりも、if let、match、あるいは map とデフォルト値を使って、両方の場合にどうするかを決めましょう。

struct User {
name: String,
}
fn find_user(id: u32) -> Option<User> {
if id == 1 {
Some(User { name: String::from("Ferris") })
} else {
None
}
}
fn main() {
// if let:User があるときだけコードを実行する
if let Some(user) = find_user(1) {
println!("見つかりました: {}", user.name);
}
// match:両方の場合を扱う
match find_user(2) {
Some(user) => println!("見つかりました: {}", user.name),
None => println!("IDが2のユーザーはいません"),
}
// map + unwrap_or:フィールドを取り出すか、デフォルト値を使う
let users = vec![User { name: String::from("Ada") }];
let first_name = users.first().map(|u| u.name.as_str()).unwrap_or("だれもいません");
println!("最初のユーザー: {first_name}");
}

実行結果:

見つかりました: Ferris
IDが2のユーザーはいません
最初のユーザー: Ada
▶ 実際に試してみよう

Option への参照にも、同じ書き方が使えます。maybe_user が &Option<User> なら、if let Some(user) = maybe_user と書けば user は &User になります(&Option<User> のときは rustc がヒントをまったく出さないので、覚えておく価値があります)。Option を返す関数の中なら、let user = find_user(1)?; でも大丈夫です。詳しくはmatchでOptionを扱うとif letを見てください。

直し方3:タプル構造体には .0 と .1 を使う

Section titled “直し方3:タプル構造体には .0 と .1 を使う”

型が struct Point(i32, i32); のようなタプル構造体なら、これを選びます。タプル構造体のフィールドには名前がなく、位置しかありません。そのため p.x と書くと no field `x` on type `Point` になります(ヒントは p.0)。位置で取り出すか、名前つきの変数に分解しましょう。名前で呼びたいなら、名前つきフィールドの構造体にします。

struct Point(i32, i32); // タプル構造体:フィールドは .0 と .1
struct Size {
width: u32, // 名前つきフィールド:.width と .height で使う
height: u32,
}
fn main() {
let p = Point(3, 4);
println!("x = {}, y = {}", p.0, p.1);
let Point(x, y) = p; // または名前つきの変数に分解する
println!("距離の2乗 = {}", x * x + y * y);
let screen = Size { width: 1920, height: 1080 };
println!("{}x{}", screen.width, screen.height);
}

実行結果:

x = 3, y = 4
距離の2乗 = 25
1920x1080
▶ 実際に試してみよう

ふつうのタプルも同じです。要素が2つのタプルで pair.2 と書くと E0609 になり、available fields are: `0`, `1`(使えるフィールドは 0 と 1)という note が付きます。詳しくは位置で要素を取り出すを見てください。

直し方4:ジェネリックな T には、フィールドの代わりにトレイトのメソッドを使う

Section titled “直し方4:ジェネリックな T には、フィールドの代わりにトレイトのメソッドを使う”

メッセージが on type `&T`(または T)で、type parameter 'T' declared here(型パラメータ T はここで宣言された)というラベルが付いているときは、これを選びます。次のようなコードは、渡すつもりの型が全部 name フィールドを持っていても、エラーになります。

fn greet<T>(animal: &T) {
println!("Hello, {}!", animal.name); // ❌ no field `name` on type `&T`
}

トレイトはフィールドを要求できませんが、ゲッターメソッドなら要求できます。小さなトレイトを定義して、それぞれの型に実装し、T の境界に付け足しましょう。

trait Named {
fn name(&self) -> &str;
}
struct Dog {
name: String,
}
struct Robot {
model: String,
}
impl Named for Dog {
fn name(&self) -> &str {
&self.name
}
}
impl Named for Robot {
fn name(&self) -> &str {
&self.model // フィールドの名前が同じである必要すらない
}
}
fn greet<T: Named>(thing: &T) {
println!("こんにちは、{}!", thing.name()); // フィールドではなくメソッドの呼び出し
}
fn main() {
greet(&Dog { name: String::from("Rex") });
greet(&Robot { model: String::from("R2-D2") });
}

実行結果:

こんにちは、Rex!
こんにちは、R2-D2!
▶ 実際に試してみよう

関数がいつも1つの型しか扱わないなら、ジェネリクスはそもそもいりません。fn greet(animal: &Dog) と書けば、animal.name が使えます。詳しくは関数の引数にトレイトを使うを見てください。

() を付け忘れたメソッド。 word.len や cart.total(total がメソッドの場合)と書くと、E0609 に近いのですが、rustc は別のコードを出します。E0615、「attempted to take value of method」(メソッドを値として取り出そうとした)で、「use parentheses to call the method」(メソッドを呼ぶにはかっこを使う)というヒントが付きます。Vec で scores.len と書くと、E0616、「field len of struct Vec is private」(Vec 構造体の len フィールドは非公開)になります。Vec には本当にその名前の隠れたフィールドがあるからです。どちらも直し方は同じで、word.len()、scores.len()、cart.total() と書きます。それから、"hello".length は &str に対するふつうの E0609 です。Rust では len() という名前です。詳しくは文字列を見てください。

1つの要素ではなく Vec だったとき。 Vec<Dog> に対して dogs.name と書くと、no field `name` on type `Vec<Dog>` になります。name を持っているのはリストではなく、一つひとつの要素です。直し方:dogs[0].name のように1つ選ぶか、for dog in &dogs { println!("{}", dog.name); } のようにループします。詳しくはベクタを見てください。

値ではなく Result だったとき。 load_config().port と書くと、no field `port` on type `Result<Config, String>` になり、同じように .unwrap() のヒントが付きます。直し方:Result を返す関数の中で let config = load_config()?; と書くか、match を使います。詳しくは?演算子を見てください。

数値のフィールド。 u32 に対して age.value と書くと、また別のコードになります。E0610、「u32 is a primitive type and therefore doesn’t have fields」(u32 はプリミティブ型なので、フィールドを持たない)です。数値、bool、char には、フィールドがまったくありません。おそらく構造体の変数のつもりだったのでしょう。

理解度チェック

1. エラーが「Option<User> 型に name というフィールドはない」と言っています。これは何を教えてくれていますか?

2. 値 m から、struct Meters(f64); の1つ目のフィールドを読むにはどう書きますか?

3. fn show<T>(x: &T) で、どの T からも名前を読めるようにするにはどうしますか?

スコア: 0 / 3