跳转到内容

Rust 错误 E0560:struct has no field named x(结构体没有这个字段)

E0560 的意思是:你在用 Name { field: value } 创建结构体,但你写的某个字段在这个结构体里并不存在。 通常是拼写错误、字段被改了名,或者这个字段其实属于另一个结构体。rustc 常常会直接告诉你本来想写的名字,所以这是修起来最快的错误之一。这个系列的其他错误,请看 Rust 编译错误详解。

error[E0560]: struct `User` has no field named `nmae`
--> src/main.rs:9:9
|
9 | nmae: String::from("Ferris"), // ❌ 拼写错误:字段名是 `name`
| ^^^^ unknown field
|
help: a field with a similar name exists
|
9 - nmae: String::from("Ferris"), // ❌ 拼写错误:字段名是 `name`
9 + name: String::from("Ferris"), // ❌ 拼写错误:字段名是 `name`
|

第一行的意思是“结构体 User 没有名为 nmae 的字段”。help: 告诉你“有一个名字相近的字段”,并给出了改好的那一行。

产生这个错误的程序是这样的:

struct User {
name: String,
email: String,
active: bool,
}
fn main() {
let user = User {
nmae: String::from("Ferris"), // ❌ 拼写错误:字段名是 `name`
email: String::from("[email protected]"),
active: true,
};
println!("{}", user.email);
}

第一行点出了结构体(User)和你写的字段(nmae)。^^^^ 指向 User { ... } 里的这个字段。Rust 拿它和 struct User 的字段列表对比了一遍,没有找到对得上的。

箭头下面会出现三种提示中的一种,每一种说明的情况都不一样:

  • help: a field with a similar name exists(有名字相近的字段):拼写错误。rustc 会给出写对名字的那一行。
  • note: available fields are: `username`(可用的字段有……):没有相近的名字。列出来的是你还没填的字段。
  • note: all struct fields are already assigned(所有字段都已经赋值了):真正的字段你都填了,这一个是多出来的。

注意,rustc 不会再另外抱怨缺少 name。改掉拼写错误,两个问题就一起解决了。

这就像填一张纸质表格时,把电话号码写进了一个叫“传真”的格子。表格上根本没有这个格子,于是工作人员把表格退了回来。

结构体的字段在你写下 struct User { ... } 的那一刻就固定了。每次创建 User,Rust 都会检查你给的字段是不是恰好就是这些:不能少,不能多,也不能拼错。参见定义和创建结构体。

这能抓出真正的 bug。在 JavaScript 里,{ nmae: "Ferris" } 会悄悄地创建一个键名写错的对象,很久以后才在某个地方发现 user.name 是 undefined。在 Rust 里,拼写错误过不了编译器这一关,所以程序里的每个 User 都真的有一个 name。

看到 a field with a similar name exists 时,选这个方法。用 help: 那一行里的名字就行。字段名区分大小写,而且 Rust 用 snake_case,所以 userName 和 user_name 是两个不同的名字。

还要留意字段初始化简写(field init shorthand):User { nmae, email } 的意思是 User { nmae: nmae, email: email },所以变量名必须和字段名一致。要么给变量改名,要么把字段完整写出来:User { name: nmae, email }。

struct User {
name: String,
email: String,
active: bool,
}
fn main() {
let user = User {
name: String::from("Ferris"), // ✅ 真正的字段名
email: String::from("[email protected]"),
active: true,
};
println!("{}({})已启用:{}", user.name, user.email, user.active);
// 简写:变量名和字段名一致
let name = String::from("Ada");
let email = String::from("[email protected]");
let other = User { name, email, active: false };
println!("{}({})已启用:{}", other.name, other.email, other.active);
}

输出:

Ferris([email protected])已启用:true
Ada([email protected])已启用:false
▶ 动手试一试

修复 2:换成字段的新名字,或者加上这个字段

Section titled “修复 2:换成字段的新名字,或者加上这个字段”

没有“相近名字”的提示时,选这个方法。很多时候是结构体变了:有人把 name 改名成了 username,或者删掉了某个字段,可这段代码还在用旧的。rustc 会列出剩下的字段:

error[E0560]: struct `User` has no field named `name`
--> src/main.rs:8:9
|
8 | name: String::from("Ferris"),
| ^^^^ `User` does not have this field
|
= note: available fields are: `username`

你有两个正当的选择。如果结构体是对的,就用它现在的字段名。如果你的代码是对的,结构体确实需要这个字段,就把它加进结构体的定义里。下面的 Config 对 debug 报了 all struct fields are already assigned,修法就是给结构体加上 debug:

struct User {
username: String, // 原来叫 `name`:用新名字
email: String,
}
struct Config {
port: u16,
host: String,
debug: bool, // 加进了结构体,因为代码需要它
}
fn main() {
let user = User {
username: String::from("ferris"),
email: String::from("[email protected]"),
};
println!("{}({})", user.username, user.email);
let config = Config {
port: 8080,
host: String::from("localhost"),
debug: true,
};
println!("{}:{} debug={}", config.host, config.port, config.debug);
}

输出:

ferris([email protected])
localhost:8080 debug=true
▶ 动手试一试

如果这个结构体来自你刚升级的某个 crate,字段可能在新版本里改了名。去 docs.rs(英文)上查看那个版本的结构体,或者看看 crate 的更新日志(changelog)。一旦给自己的结构体加了字段,每个创建它的地方也都得填上这个字段。rustc 会用 E0063 逐一指出这些地方(见下文)。

当你写的字段确实存在,只是在另一个结构体里时,选这个方法。如果有两个相似的结构体,比如带 title 的 Product 和带 name 的 Customer,很容易把它们搞混:

error[E0560]: struct `Product` has no field named `name`
--> src/main.rs:13:9
|
13 | name: String::from("Keyboard"),
| ^^^^ `Product` does not have this field
|
= note: available fields are: `title`

看看 available fields 那条 note。如果列出来的字段出乎你的意料,说明你在构建错误的结构体。换成正确的结构体名,或者用你本来想要的那个结构体的字段名:

struct Product {
title: String,
price: u32, // 单位是分
}
struct Customer {
name: String,
email: String,
}
fn main() {
let keyboard = Product {
title: String::from("Keyboard"), // 商品有 `title`
price: 4900,
};
let buyer = Customer {
name: String::from("Ana"), // 顾客有 `name`
email: String::from("[email protected]"),
};
println!("{} 买了 {},价格 {} 分", buyer.name, keyboard.title, keyboard.price);
println!("收据已发送到 {}", buyer.email);
}

输出:

Ana 买了 Keyboard,价格 4900 分
收据已发送到 [email protected]
▶ 动手试一试

当结构体是元组结构体(tuple struct),比如 struct Point(i32, i32); 时,选这个方法。它的字段没有名字,所以 Point { x: 1, y: 2 } 会对 x 和 y 都报 E0560,还会给出 help: `Point` is a tuple struct, use the appropriate syntax(Point 是元组结构体,请用相应的语法)和建议写法 Point(/* i32 */, /* i32 */)。用圆括号创建它:Point(1, 2),再用 .0 和 .1 读取。如果你更想要名字,就把定义改成 struct Point { x: i32, y: i32 }。参见什么时候用元组,什么时候用结构体。

E0560 的“双胞胎”是 E0063:结构体有这个字段,但你没有填。Rust 从不替你补字段,所以每个字段都需要一个值:

error[E0063]: missing field `active` in initializer of `User`
--> src/main.rs:8:16
|
8 | let user = User {
| ^^^^ missing `active`

如果缺了好几个字段,它会全部列出来:missing fields `active`, `email` and `login_count` in initializer of `User`。修复:给每个字段都赋值。如果很多字段都有合理的默认值,就 derive Default,再用结构体更新语法 .. 把剩下的补上:

#[derive(Debug, Default)]
struct Settings {
volume: u8,
dark_mode: bool,
language: String,
}
fn main() {
// 每个字段都写出来
let full = Settings { volume: 80, dark_mode: false, language: String::from("en") };
// 只写你关心的字段,其余的来自 Default
let quick = Settings { dark_mode: true, ..Default::default() };
// 其余的从另一个值里复制过来
let louder = Settings { volume: 100, ..full };
println!("{:?}", quick);
println!("{:?}", louder);
}

输出:

Settings { volume: 0, dark_mode: true, language: "" }
Settings { volume: 100, dark_mode: false, language: "en" }
▶ 动手试一试

数字的默认值是 0,bool 是 false,String 是空字符串。如果你想要别的默认值,就自己写 impl Default for Settings。参见 Default:合理的初始值。

在 .. 更新语法里。 Settings { dark: true, ..defaults } 同样会报 E0560。这时 note 会列出所有字段(available fields are: `volume`, `dark_mode`, `language`),如果名字差得太远,可能也不会有“相近名字”的提示。修复:dark_mode: true。

带命名字段的枚举变体。 对于 enum Shape { Circle { radius: f64 } },写 Shape::Circle { r: 2.0 } 是同样的错误,但 rustc 用的是另一个代码:E0559,variant `Shape::Circle` has no field named `r`,同样带有 available fields 的 note 和拼写提示。修复:Shape::Circle { radius: 2.0 }。参见枚举可以携带数据。

来自其他模块的结构体。 如果字段存在但不是 pub 的,你得到的会是 E0451:“field balance of struct Account is private”(字段是私有的)。修复:用模块提供的构造函数,比如 Account::new(...)。参见默认一切都是私有的。

小测验

1. rustc 说 note: all struct fields are already assigned。这是什么意思?

2. 你有 let nmae = ...;,然后写了 User { nmae, email }。怎么修?

3. 漏掉一个字段时,比如写了 User { name, email } 却没有 active,会得到哪个错误?

得分: 0 / 3