Rust 错误 E0063:missing field in initializer(缺少结构体字段)
E0063 的意思是:你在用 Name { ... } 创建一个结构体,但没有给每个字段都赋值。 Rust 从不替你填字段,所以每个字段都需要一个值,哪怕只是 0、false 或 None。错误信息会按名字列出缺少的字段,你一看就知道该补什么。这是 Rust 里最友好的错误之一。(本页属于 Rust 编译错误详解系列。)
error[E0063]: missing field `email` in initializer of `User` --> src/main.rs:8:16 |8 | let user = User { | ^^^^ missing `email`意思是:User 的初始化里缺少字段 email。
产生这个错误的程序是这样的:
struct User { name: String, email: String, active: bool,}
fn main() { let user = User { name: String::from("Ferris"), active: true, // ❌ 没有 `email` }; println!("{}", user.name);}这个错误是什么意思
Section titled “这个错误是什么意思”标题里写出了缺少的字段(email)和结构体(User)。^^^^ 指向你开始构建它的那个结构体名,而不是某一行,因为问题在于有东西不在那里。
缺少的字段更多时,rustc 会按字母顺序列出来。缺两三个时,你会看到全部:
error[E0063]: missing fields `debug`, `retries` and `timeout_secs` in initializer of `Config`缺四个或更多时,它只显示三个,剩下的给出数量:
error[E0063]: missing fields `debug`, `port`, `retries` and 1 other field in initializer of `Config` --> src/main.rs:10:18 |10 | let config = Config { | ^^^^^^ missing `debug`, `port`, `retries` and 1 other field“1 other field”的意思是还有 1 个。去看 struct Config 的定义,就能看到完整的列表。
就像一张每一栏都必填的表格。即使你打算以后再填,也不能空着“邮箱”那一栏就交上去。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”在很多语言里,缺少的字段会悄悄变成 null、0 或空字符串。于是程序带着一个没有邮箱的用户跑了起来,然后在很远的地方出问题,而且常常是在生产环境里。Rust 没有 null,也不会背着你编造值。一个 User 永远有一个真正的 email。参见定义和创建结构体。
这并不意味着你每次都得把所有字段敲一遍。Rust 提供了简短的写法,表示“使用默认值”或“其余的从另一个值复制过来”,下面的修复方法会讲到。关键在于由你来选,而且这个选择就写在代码里。
修复 1:补上缺少的字段
Section titled “修复 1:补上缺少的字段”大多数时候都选这个。这个字段存在是有原因的,所以给它一个真正的值。如果你有和字段同名的变量,可以用简写 User { name, email, active }。
struct User { name: String, email: String, active: bool,}
fn main() { let user = User { name: String::from("Ferris"), active: true, }; println!("{} <{}> 活跃:{}", user.name, user.email, user.active);}输出:
Ferris <[email protected]> 活跃:true修复 2:derive Default,再用 ..Default::default()
Section titled “修复 2:derive Default,再用 ..Default::default()”当结构体字段很多,而且大部分字段都有一个明显的“空”值时(比如设置或选项),选这个。给结构体加上 #[derive(Default)],然后只写你关心的字段,最后加上 ..Default::default()。.. 的意思是“我没写的字段,全都从这个值里取”。
#[derive(Debug, Default)]struct Config { host: String, port: u16, debug: bool, timeout_secs: u64, retries: u8,}
fn main() { let config = Config { host: String::from("localhost"), port: 8080, ..Default::default() // ✅ debug、timeout_secs 和 retries 取默认值 }; println!("{:?}", config);}输出:
Config { host: "localhost", port: 8080, debug: false, timeout_secs: 0, retries: 0 }derive 出来的默认值是:数字为 0,bool 为 false,String 为空字符串,Option 为 None。如果 0 不是个好的默认值(超时 0 秒多半不是),就自己写一个 impl Default for Config,给出更合适的值。参见 Default:合理的初始值。
如果忘了写 #[derive(Default)],错误会变成 E0277:the trait bound `Config: Default` is not satisfied(Config 不满足 Default 约束),并附带 help: consider annotating `Config` with `#[derive(Default)]`(考虑给 Config 加上 #[derive(Default)])。
修复 3:写一个构造函数 new
Section titled “修复 3:写一个构造函数 new”当你在很多地方创建这个结构体,或者某些字段总是以同样的值开始时,选这个。把完整的结构体字面量放进一个函数里,其他地方都调用这个函数。以后加字段时,只需要改 new,不用改每一个调用的地方。
struct Player { name: String, score: u32, level: u8,}
impl Player { fn new(name: &str) -> Player { Player { name: name.to_string(), score: 0, // 每个新玩家都从 0 分开始…… level: 1, // ……在第 1 级 } }}
fn main() { let ana = Player::new("Ana"); let mut bo = Player::new("Bo"); bo.score += 50; println!("{}:{} 分,第 {} 级", ana.name, ana.score, ana.level); println!("{}:{} 分,第 {} 级", bo.name, bo.score, bo.level);}输出:
Ana:0 分,第 1 级Bo:50 分,第 1 级new 里面的结构体字面量仍然需要每个字段,所以漏掉的字段会在那里以 E0063 的形式出现。这没关系:你只需要修一次。参见构造函数风格的函数。
修复 4:用 ..other 从另一个值复制其余字段
Section titled “修复 4:用 ..other 从另一个值复制其余字段”当你已经有一个相似的值,想要一份只改了几处的副本时,选这个。..base 会把你没写的字段全都从 base 里取:
#[derive(Debug, Clone)]struct Theme { name: String, font_size: u8, dark: bool,}
fn main() { let base = Theme { name: String::from("默认"), font_size: 14, dark: false };
// 只改 `name` 和 `dark`,其余的来自 `base` let night = Theme { name: String::from("夜间"), dark: true, ..base };
// `..base` 会把 `name` 从 base 里移动出来,所以先克隆,才能继续使用 base let large = Theme { font_size: 20, ..base.clone() };
println!("{:?}", night); println!("{:?}", large); println!("{:?}", base);}输出:
Theme { name: "夜间", font_size: 14, dark: true }Theme { name: "默认", font_size: 20, dark: false }Theme { name: "默认", font_size: 14, dark: false }有一点要知道:..base 会把 String 这样的字段从 base 里移动出来。在 night 里这没问题,因为我们自己写了 name,从 base 取来的只有 Copy 的字段(font_size)。在 large 里,name 会来自 base,所以我们用 ..base.clone(),让 base 还能继续用。如果不克隆,之后又用了 base,就会得到 E0382。
Option 字段什么时候有用
Section titled “Option 字段什么时候有用”有时候一个字段确实可以为空:用户还没填的电话号码、昵称、结束日期。把这样的字段定义成 Option。你还是得把它写出来(Rust 不会替你填 None),但现在 None 是一个诚实的、被允许的值。参见 Option。
#[derive(Debug, Default)]struct Contact { name: String, email: String, phone: Option<String>, // 可能没有 nickname: Option<String>,}
fn main() { let a = Contact { name: String::from("Ana"), phone: None, // 明确写出来:“还没有电话” nickname: Some(String::from("Annie")), }; // 有了 Default,你省略的每个 Option 都会变成 None let b = Contact { name: String::from("Bo"), ..Default::default() };
println!("{} 的电话:{:?},昵称:{:?}", a.name, a.phone, a.nickname); println!("{} 的邮箱:{:?},电话:{:?}", b.name, b.email, b.phone);}输出:
Ana 的电话:None,昵称:Some("Annie")Bo 的邮箱:"",电话:None注意 b.email 是 ""。这就是 Default 的陷阱:一个必填的字段悄悄变成了空的。只对真正可选的东西用 Option,像 email 这样的必填字段就保持普通类型,每次都填上。
刚给结构体加了一个字段之后。 你给 User 加了 created_at,项目里的每一个 User { ... } 突然都报 E0063。这是 Rust 在帮你:每一处都是你需要考虑的地方。用 new 构造函数(修复 3)可以让这个列表保持很短。
带命名字段的枚举变体。 Shape::Rectangle { width: 2.0 } 会报 missing field `height` in initializer of `Shape`。注意,信息里写的是枚举名,而不是变体名。修复:给这个变体的每个字段都赋值。参见枚举可以携带数据。
返回结构体的函数。 在 fn new_player(name: &str) -> Player { Player { name: name.to_string(), score: 0 } } 里,箭头指向函数里的 Player,并显示 missing `level`。修复:在那个字面量里补上 level。
- E0560:struct has no field named x:反过来的情况,你写了一个结构体没有的字段。一个拼写错误常常会同时引发这两个错误。
- E0559:variant has no field named x:枚举变体上同样的“字段名写错”错误。
- E0277:trait bound not satisfied:你写了
..Default::default(),但结构体没有 deriveDefault。 - E0451:field is private:结构体来自另一个模块,有些字段是私有的,所以你没法填它们。如果你省略了私有字段,得到的不是 E0063,而是“cannot construct
Accountwith struct literal syntax due to private fields”(因为有私有字段,不能用结构体字面量语法构造Account)。不管哪种情况,都请用那个模块提供的new函数。 - E0382:use of moved value:
..value把字段移动出来以后,你又用了这个值。 - 所有编译错误详解
小测验
1. rustc 说:missing fields a, b, c and 2 other fields。一共缺了几个字段?
2. 你的结构体有一个暂时用不到的 Option<String> 字段。你该给它写什么?
3. 要让 Config { port: 80, ..Default::default() } 通过编译,需要什么?
得分: 0 / 3
- E0063 — Rust error codes index(英文):这个错误的官方解释。
- 结构体的定义和实例化 — Rust 程序设计语言:结构体字面量、字段初始化简写和结构体更新语法。
Default— 标准库文档(英文):#[derive(Default)]、自己编写impl Default,以及..Default::default()。- Struct expressions — The Rust Reference(英文):
Name { field: value }和..base的确切规则。 - Constructors — Rust API Guidelines(英文):为什么 Rust 代码用
new函数来构建结构体。