Rust 错误 E0609:no field on type(类型上没有该字段)
E0609 的意思是:你写了 value.something,可 value 的类型里没有叫 something 的字段。 大多数时候是拼错了,或者这个值并不完全是你以为的那个类型(比如它是 Option<User>,而不是 User)。报错信息会告诉你 Rust 看到的确切类型,通常靠这一点就能修好。(本页属于 Rust 编译错误详解系列。)
error[E0609]: no field `emial` on type `User` --> src/main.rs:12:25 |12 | println!("{}", user.emial); // ❌ typo: the field is `email` | ^^^^^ unknown field |help: a field with a similar name exists |12 - println!("{}", user.emial); // ❌ typo: the field is `email`12 + println!("{}", user.email); // ❌ typo: the field is `email` |第一行的意思是“类型 User 上没有字段 emial”。help 提示你“有一个名字相近的字段”。
产生这个错误的程序是这样的:
struct User { name: String, email: String,}
fn main() { let user = User { name: String::from("Ferris"), }; println!("{}", user.name); println!("{}", user.emial); // ❌ typo: the field is `email`}这个错误是什么意思
Section titled “这个错误是什么意思”第一行有两部分,两部分都重要:
no field `emial`:点号后面那个 Rust 找不到的名字。on type `User`:Rust 认为user是什么类型。
所以先检查名字,再检查类型。如果信息里的类型不是你期望的那个结构体,比如是 Option<User>、&Option<User>、Vec<User> 或 &T,那名字其实没错,问题在于你得先把结构体取出来。
就像你走进一家店,问“第二层架子上那个蓝色的”,可你走错了店。要的东西没问题,只是地方不对。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”在 Rust 里,每个结构体有哪些字段,在你写下结构体时就定好了。编译器知道完整的字段列表,并在编译时检查每一个 .字段。这里没有 JavaScript 那种“运行时去找,找不到就得到 undefined”,也没有 Python 那种 AttributeError:写错的字段名根本进不了你的程序。参见结构体。
Option<T> 和 Result<T, E> 这样的包装类型是独立的类型:你的结构体在它们里面,但它们自己并没有那些字段。Rust 不会替你伸手进去拿,因为里面可能根本没有值。值不存在时该怎么办,得由你说清楚。参见 Option。
泛型代码也遵守同样严格的规则。在 fn greet<T>(x: &T) 里,T 可以是任何类型,包括没有字段的 i32 或 bool。Rust 对一个泛型函数只检查一次,而且要对所有可能的 T 都成立,所以它只允许你使用 trait 约束承诺过的东西。而 trait 只能承诺方法,不能承诺字段。参见泛型和 trait。
修复 1:改正拼写
Section titled “修复 1:改正拼写”当信息里的类型就是你期望的结构体时,选这个。找找 help: a field with a similar name exists(有一个名字相近的字段)这一行,rustc 通常很清楚你想要的是哪个字段:
struct User { name: String, email: String,}
fn main() { let user = User { name: String::from("Ferris"), }; println!("{}", user.name); println!("{}", user.email); // ✅ 真正的字段名}输出:
Ferris如果没有提示,就跳到 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 的意思是“表达式里的某个值有同名的字段”。照着 .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!("距离的平方 = {}", x * x + y * y);
let screen = Size { width: 1920, height: 1080 }; println!("{}x{}", screen.width, screen.height);}输出:
x = 3, y = 4距离的平方 = 251920x1080普通元组也是一样。在只有两个元素的元组上写 pair.2,会得到 E0609,并附上 note:available fields are: `0`, `1`(可用的字段是 0 和 1)。参见按位置访问元素。
修复 4:在泛型 T 上用 trait 方法代替字段
Section titled “修复 4:在泛型 T 上用 trait 方法代替字段”当信息说 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`}trait 不能要求字段,但可以要求一个取值方法(getter)。定义一个小 trait,为每个类型实现它,再把它作为 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!如果这个函数永远只处理一种类型,就根本不需要泛型:写成 fn greet(animal: &Dog),animal.name 就能用了。参见把 trait 用作函数参数。
方法忘了加 ()。 写成 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()。参见字符串。
拿到的是 Vec,不是单个元素。 在 Vec<Dog> 上写 dogs.name,会得到 no field `name` on type `Vec<Dog>`。列表本身没有 name,每个元素才有。修复:用 dogs[0].name 取其中一个,或者写个循环:for dog in &dogs { println!("{}", dog.name); }。参见 vector。
拿到的是 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 根本没有字段,你想用的多半是某个结构体变量。
- E0599:no method named x found:这个错误的方法版本。原因常常相同:拼错了,或者有个
Option需要先取出值。 - E0425:cannot find value in this scope:点号前面的变量根本不存在,或者你在字段前面忘了写
self.。 - E0308:mismatched types:如果你在一处取出了
Option的值,另一处却没有,接下来常常会看到它。 E0615: attempted to take value of method:你写的是x.len,而不是x.len()。E0616: field is private:字段存在,但不是pub的,比如Vec上的len。请使用这个类型的方法。E0560: struct has no field named x:同样的拼写错误,只是发生在创建结构体的时候(User { emial: ... })。E0610: primitive type doesn't have fields:在数字、bool或char上写了.something。- 所有编译错误:错误页面的完整列表。
小测验
1. 报错说类型 Option<User> 上没有字段 name。这说明了什么?
2. 对于 struct Meters(f64); 类型的值 m,怎样读取它的第一个字段?
3. 在 fn show<T>(x: &T) 里,怎样从每个 T 读取一个名字?
得分: 0 / 3
- E0609 — Rust error codes index(英文):这个错误的官方解释。
- 结构体的定义和实例化 — Rust 程序设计语言:命名字段、用点号访问字段,以及结构体更新语法。
- 使用元组结构体创建不同的类型 — Rust 程序设计语言:按位置(比如
.0)访问字段的结构体。 - Option 枚举 — Rust 程序设计语言:为什么
Option<T>和T是不同的类型,以及为什么必须先把值取出来。 - Trait:定义共同行为 — Rust 程序设计语言:trait 方法和 trait 约束,写泛型代码的正确工具。