Ошибка 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 type20 | 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 — это и есть такая анкета.
Почему Rust так делает
Заголовок раздела «Почему Rust так делает»Поля структур по умолчанию приватные, как функции и модули (смотри Модули). Если бы внешний код мог создать 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);}Вывод:
#f74c00pub нужен каждому полю. Если создавать структуру должен только твой собственный крейт, подойдёт и 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
Что почитать
Заголовок раздела «Что почитать»- E0451 — Rust error codes index (на английском): официальное объяснение с минимальным примером.
- Делаем общедоступными структуры и перечисления — книга «Язык программирования Rust» (перевод на русский): структура с приватным полем, которой нужен публичный конструктор.
- Видимость структуры — Rust на примерах (перевод на русский): как создавать структуры с публичными и приватными полями снаружи модуля.
- Builder pattern — Rust Design Patterns (на английском): полный паттерн «строитель» с отдельным типом-строителем.
- Трейт
Default— стандартная библиотека Rust (на английском): как выводить и реализовывать значения по умолчанию.