Перейти к содержимому

Ошибка Rust E0451: field of struct is private (литерал структуры)

E0451 значит: ты попытался создать структуру литералом вроде Account { ... }, но одно из её полей приватное. В литерале структуры нужно заполнить каждое поле, так что одного приватного поля достаточно, чтобы тебя не пустили. Это сделано нарочно: модуль хочет, чтобы ты шёл через его собственный конструктор. Почти всегда исправление — вызвать (или добавить) функцию new. Эта страница — часть справочника по ошибкам компилятора.

error[E0451]: field `balance` of struct `Account` is private
--> src/main.rs:21:9
|
19 | let account = bank::Account {
| ------------- in this type
20 | owner: String::from("Ferris"),
21 | balance: 1_000_000, // ❌ private field
| ^^^^^^^ private field

Компилятор говорит: поле balance структуры Account приватное, а ты задаёшь его в литерале. Вот программа, которая выдаёт эту ошибку:

mod bank {
pub struct Account {
pub owner: String,
balance: i64, // private
}
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, // ❌ private field
};
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

Исправления у всех одни и те же.

Стрелка показывает на поле, которое тебе нельзя задавать: balance. owner помечено pub, его задавать можно. Но литерал структуры работает по принципу «всё или ничего». Пропустить balance нельзя (Rust не заполняет недостающие поля), а задать его тоже нельзя. Так что снаружи mod bank написать Account { ... } попросту невозможно.

Представь, что открываешь настоящий банковский счёт. Тебе не дают самому вписать в анкету начальный баланс. Ты пишешь своё имя, банк создаёт счёт и сам решает, что баланс начинается с нуля. Account::new — это и есть такая анкета.

Поля структур по умолчанию приватные, как функции и модули (смотри Модули). Если бы внешний код мог создать Account с любым балансом, правила модуля ничего бы не значили. Кто-нибудь мог бы создать счёт с миллионом или с минус пятьюдесятью. Поскольку литерал может писать только mod bank, каждый Account в программе создаёт код, который знает правила.

Есть и второе преимущество. Если потом ты добавишь приватное поле, например created_at или history, код снаружи модуля не сломается, потому что он никогда не писал литерал. Он вызывает new, а new ты обновляешь в одном месте.

Исправление 1: используй (или добавь) конструктор pub fn new

Заголовок раздела «Исправление 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 { ... }. Не забудь pub у самой new: без него получишь E0624 (associated function `new` is private). Если какие-то входные данные надо отклонять, пусть new возвращает Option<Account> или Result<Account, String>; пример есть на странице E0603. Подробнее о конструкторах — в разделе Функции-конструкторы.

Исправление 2: сделай поля pub, когда данные простые

Заголовок раздела «Исправление 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 нужен каждому полю. Если создавать структуру должен только твой собственный крейт, подойдёт и pub(crate) у полей.

Когда лучше оставить поле приватным: когда значение может оказаться неправильным (отрицательный баланс, процент больше 100, email без @) или когда два поля должны согласовываться друг с другом (total, который должен равняться сумме items). Тогда литерал позволил бы обойти проверку, и правильный выбор — исправление 1.

Исправление 3: начни с Default, потом поменяй публичные поля

Заголовок раздела «Исправление 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)

Заголовок раздела «Исправление 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, потому что правило живёт в одном месте. Так делают многие крейты, иногда с отдельным типом ConfigBuilder и финальным вызовом .build(). Этот вариант разобран в уроке Паттерны проектирования.

После переноса структуры в модуль. Каждый User { name, email } в main.rs работал, пока User не переехал в mod user (или src/user.rs). Теперь все они падают. Решай для каждой структуры отдельно: простым данным — поля pub (исправление 2), всему, у чего есть правила, — pub fn new (исправление 1).

Типы из стандартной библиотеки или крейта. Нельзя написать Vec { ... } или Duration { ... }. rustc скажет cannot construct `Vec<_, _>` with struct literal syntax due to private fields и перечислит функции вроде Vec::new() и Vec::with_capacity(_). Используй их или Duration::from_secs(5).

Структура #[non_exhaustive] из другого крейта. Здесь не помогают даже публичные поля, и код ошибки другой: error[E0639]: cannot create non-exhaustive struct using struct expression. Крейт хочет добавлять поля позже, не ломая твой код, поэтому используй его конструктор или 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 { verbose: true, ..Default::default() } не компилируется, если у Config есть приватное поле?

3. Какой структуре хорошо подходят поля pub и простые литералы?

Результат: 0 / 3