跳转到内容

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"),
email: String::from("[email protected]"),
};
println!("{}", user.name);
println!("{}", user.emial); // ❌ typo: the field is `email`
}

第一行有两部分,两部分都重要:

  • no field `emial`:点号后面那个 Rust 找不到的名字。
  • on type `User`:Rust 认为 user 是什么类型。

所以先检查名字,再检查类型。如果信息里的类型不是你期望的那个结构体,比如是 Option<User>、&Option<User>、Vec<User> 或 &T,那名字其实没错,问题在于你得先把结构体取出来。

就像你走进一家店,问“第二层架子上那个蓝色的”,可你走错了店。要的东西没问题,只是地方不对。

在 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。

当信息里的类型就是你期望的结构体时,选这个。找找 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 的意思是“表达式里的某个值有同名的字段”。照着 .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。

当类型是元组结构体(比如 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
距离的平方 = 25
1920x1080
▶ 动手试一试

普通元组也是一样。在只有两个元素的元组上写 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 根本没有字段,你想用的多半是某个结构体变量。

小测验

1. 报错说类型 Option<User> 上没有字段 name。这说明了什么?

2. 对于 struct Meters(f64); 类型的值 m,怎样读取它的第一个字段?

3. 在 fn show<T>(x: &T) 里,怎样从每个 T 读取一个名字?

得分: 0 / 3