Rust 错误 E0451:field of struct is private(构造时遇到私有字段)
E0451 的意思是:你想用 Account { ... } 这样的字面量来构建结构体,但它有一个字段是私有的。 写结构体字面量必须把每个字段都填上,所以只要有一个私有字段,你就被挡在门外了。这是故意的:模块希望你通过它自己的构造函数来创建。修法几乎总是调用(或者添加)一个 new 函数。这一页属于 Rust 编译错误详解系列。
error[E0451]: field `balance` of struct `Account` is private --> src/main.rs:21:9 |19 | let account = bank::Account { | ------------- in this type20 | owner: String::from("Ferris"),21 | balance: 1_000_000, // ❌ 私有字段 | ^^^^^^^ private field第一行的意思是“结构体 Account 的字段 balance 是私有的”。in this type 标出了你正在构建的类型。
产生这个错误的程序是这样的:
mod bank { pub struct Account { pub owner: String, balance: i64, // 私有 }
impl Account { pub fn new(owner: &str) -> Account { Account { owner: owner.to_string(), balance: 0 } }
pub fn balance(&self) -> i64 { self.balance } }}
fn main() { let account = bank::Account { owner: String::from("Ferris"), balance: 1_000_000, // ❌ 私有字段 }; println!("{} has {}", account.owner, account.balance());}你也可能看到它的几个近亲:
| 你写的是 | rustc 怎么说 |
|---|---|
| 字面量里写了私有字段 | error[E0451]: field `balance` of struct `Account` is private |
Config { verbose: true, ..Default::default() } |
同样是 E0451,指向 ..Default::default()(见修复 3) |
| 字面量里没写私有字段 | cannot construct `Account` with struct literal syntax due to private fields,常常还带有 help: you might have meant to use the `new` associated function |
给元组结构体写 Percent(150) |
E0603:tuple struct constructor `Percent` is private |
它们的修法都一样。
这个错误是什么意思
Section titled “这个错误是什么意思”箭头指向你无权设置的字段:balance。owner 是 pub 的,所以设置它没问题。但结构体字面量是“要么全有,要么全无”的:你不能把 balance 省掉(Rust 不会替你补上缺的字段),也不能给它赋值。所以在 mod bank 外面,根本没有办法写出 Account { ... }。
想象一下在银行开户。你不能在表格上自己填写初始余额。你填上自己的名字,银行创建账户,由银行决定余额从零开始。Account::new 就是那张表格。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”和函数、模块一样,结构体字段默认也是私有的(参见模块)。如果外面的代码能随意用任何余额来构建 Account,模块定下的规则就形同虚设了。有人可以创建一个有一百万的账户,或者一个负五十的账户。正因为只有 mod bank 能写这个字面量,程序里的每一个 Account 都是由了解规则的代码创建的。
还有第二个好处。如果你以后要加一个私有字段,比如 created_at 或 history,模块外面的代码不会坏,因为它从来没写过字面量。它调用的是 new,你只需在一个地方更新 new。
修复 1:使用(或添加)pub fn new 构造函数
Section titled “修复 1:使用(或添加)pub fn new 构造函数”大多数时候都选这个方法。先找找有没有 new(或 from_...、with_...)函数,rustc 常常会给你指出来。如果这是你自己的结构体,而且还没有,就写一个:
mod bank { pub struct Account { pub owner: String, balance: i64, // 私有 }
impl Account { // 每个账户都从 0 开始。钱只能通过 deposit() 存进来。 pub fn new(owner: &str) -> Account { Account { owner: owner.to_string(), balance: 0 } }
pub fn deposit(&mut self, amount: i64) { if amount > 0 { self.balance += amount; } }
pub fn balance(&self) -> i64 { self.balance } }}
fn main() { let mut account = bank::Account::new("Ferris"); account.deposit(250); println!("{} 的余额是 {}", account.owner, account.balance());}输出:
Ferris 的余额是 250在 mod bank 里面可以写字面量,所以 new 可以随意写 Account { ... }。别忘了给 new 本身也加上 pub,否则你会得到 E0624(associated function `new` is private)。如果某些输入应该被拒绝,就让 new 返回 Option<Account> 或 Result<Account, String>,E0603 页面有一个例子。更多关于构造函数的内容见结构体。
修复 2:数据很简单时,把字段设为 pub
Section titled “修复 2:数据很简单时,把字段设为 pub”当字段的任何组合都没问题时,选这个方法。颜色不过是三个数字,不存在需要防范的“错误”颜色。这时用字面量构建它是最清楚的写法:
mod theme { pub struct Color { pub r: u8, pub g: u8, pub b: u8, }}
fn main() { let ferris_orange = theme::Color { r: 247, g: 76, b: 0 }; println!("#{:02x}{:02x}{:02x}", ferris_orange.r, ferris_orange.g, ferris_orange.b);}输出:
#f74c00每个字段都需要 pub。如果只应该由你自己的 crate 来构建它,给字段加 pub(crate) 也可以。
什么时候应该让字段保持私有: 当某个值可能是错的(负数余额、超过 100 的百分比、没有 @ 的邮箱),或者两个字段必须彼此一致(total 必须等于 items 的总和)时。这时字面量会让人跳过检查,修复 1 才是正确的选择。
修复 3:先从 Default 开始,再修改公有字段
Section titled “修复 3:先从 Default 开始,再修改公有字段”这适合那些有合理默认值、部分字段公有、部分字段私有的“设置”类结构体。先说陷阱。结构体更新语法 ..Default::default() 看起来应该能用,但它不行:
mod settings { pub struct Config { pub verbose: bool, retries: u32, // 私有:必须保持在 1 到 10 之间 }
impl Default for Config { fn default() -> Config { Config { verbose: false, retries: 3 } } }}
fn main() { let config = settings::Config { verbose: true, ..Default::default() }; println!("{}", config.verbose);}error[E0451]: field `retries` of struct `Config` is private --> src/main.rs:15:54 |15 | let config = settings::Config { verbose: true, ..Default::default() }; | ^^^^^^^^^^^^^^^^^^ field `retries` is private..Default::default() 仍然是结构体字面量:它会把 retries 复制进新结构体,这就算是给它赋值了。正确的做法是先创建默认值,然后再修改公有字段:
mod settings { pub struct Config { pub verbose: bool, retries: u32, // 私有:必须保持在 1 到 10 之间 }
impl Default for Config { fn default() -> Config { Config { verbose: false, retries: 3 } } }
impl Config { pub fn retries(&self) -> u32 { self.retries } }}
fn main() { let mut config = settings::Config::default(); config.verbose = true; // 公有字段可以在之后修改 println!("verbose: {}, retries: {}", config.verbose, config.retries());}输出:
verbose: true, retries: 3如果每个字段都是 pub 的,..Default::default() 就完全没问题。挡住它的只有私有字段。
修复 4:提供 builder 风格的方法
Section titled “修复 4:提供 builder 风格的方法”当选项有好几个、其中一些还有规则时,选这个方法。每个方法接收结构体,修改一项设置(并做检查),再把结构体交回来,所以这些调用可以很顺地串起来:
mod settings { pub struct Config { verbose: bool, retries: u32, }
impl Config { pub fn new() -> Config { Config { verbose: false, retries: 3 } }
pub fn verbose(mut self, on: bool) -> Config { self.verbose = on; self }
// 规则就写在这里:重试 1 到 10 次。 pub fn retries(mut self, n: u32) -> Config { self.retries = n.clamp(1, 10); self }
pub fn describe(&self) -> String { format!("verbose: {}, retries: {}", self.verbose, self.retries) } }}
fn main() { let config = settings::Config::new().verbose(true).retries(50); println!("{}", config.describe());}输出:
verbose: true, retries: 10要求重试 50 次,结果悄悄变成了 10 次,因为规则只写在一个地方。很多 crate 都这样做,有时还会用一个单独的 ConfigBuilder 类型,最后调用一次 .build()。那种写法见设计模式。
把结构体挪进模块之后。 main.rs 里的每一个 User { name, email } 原本都好好的,直到 User 被挪进了 mod user(或 src/user.rs),现在全都报错。逐个结构体来决定:普通数据就给字段加 pub(修复 2);有规则的就加一个 pub fn new(修复 1)。
来自标准库或 crate 的类型。 你没法写 Vec { ... } 或 Duration { ... }。rustc 会说 cannot construct `Vec<_, _>` with struct literal syntax due to private fields(因为有私有字段,不能用结构体字面量构建),并列出 Vec::new()、Vec::with_capacity(_) 这样的函数。用这些函数,或者 Duration::from_secs(5)。
来自其他 crate 的 #[non_exhaustive] 结构体。 这时连公有字段都帮不上忙,错误代码也不一样:error[E0639]: cannot create non-exhaustive struct using struct expression。这个 crate 想在以后加字段时不破坏你的代码,所以请用它的构造函数或 Default,做法和修复 1、修复 3 完全一样。
- E0616:同样是私有字段,只不过发生在读取它(
account.balance)时。 - E0603:
tuple struct constructor `X` is private(元组结构体的构造器是私有的),以及私有的函数、结构体或模块。 - E0624:
associated function `new` is private。你的构造函数也需要pub。 - E0560:
struct `X` has no field named `y`。字面量里的字段名拼错了。 - E0063(英文):
missing field `x` in initializer of `Y`。你漏掉了一个公有字段。 - 所有编译错误:错误页面的完整列表。
小测验
1. pub struct Ticket { pub title: String, id: u32 } 写在 mod tickets 里。main 怎样才能创建一个 Ticket?
2. 当 Config 有私有字段时,为什么 Config { verbose: true, ..Default::default() } 会失败?
3. 下面哪个结构体适合全部用 pub 字段和普通字面量?
得分: 0 / 3
- E0451 — Rust error codes index(英文):这个错误的官方说明,附有一个最小示例。
- 创建公有的结构体和枚举 — Rust 程序设计语言:一个带私有字段、因而需要公有构造函数的结构体。
- 结构体的可见性 — 通过例子学 Rust:在模块外面构建带有公有和私有字段的结构体。
- Builder pattern — Rust Design Patterns(英文):带有单独 builder 类型的完整 builder 模式。
Defaulttrait — 标准库文档(英文):derive 和实现默认值。