Ir al contenido

Error E0451 de Rust: field of struct is private (campo privado al crear)

E0451 significa: intentaste construir un struct con un literal como Account { ... }, pero uno de sus campos es privado. Para escribir un struct literal tienes que rellenar todos los campos, así que basta con un campo privado para que te quedes fuera. Es a propósito: el módulo quiere que pases por su propio constructor. La solución casi siempre es llamar a una función new (o añadirla). Esta página forma parte de la guía de errores del compilador.

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, // ❌ campo privado
| ^^^^^^^ private field

“Field balance of struct Account is private” quiere decir “el campo balance del struct Account es privado”. La línea in this type señala el struct que estás construyendo, y private field marca el campo que no puedes asignar.

Este es el programa que lo produce:

mod bank {
pub struct Account {
pub owner: String,
balance: i64, // privado
}
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, // ❌ campo privado
};
println!("{} has {}", account.owner, account.balance());
}

Puede que veas alguno de sus primos cercanos:

Lo que escribiste Lo que dice rustc
el campo privado dentro del literal error[E0451]: field `balance` of struct `Account` is private
Config { verbose: true, ..Default::default() } también E0451, señalando ..Default::default() (mira la Solución 3)
el literal sin el campo privado cannot construct `Account` with struct literal syntax due to private fields, a menudo con help: you might have meant to use the `new` associated function
Percent(150) en un tuple struct E0603: tuple struct constructor `Percent` is private

El tercer mensaje dice que no puedes construir Account con la sintaxis de struct literal porque tiene campos privados, y el help sugiere que quizá querías usar la función asociada new. Todos tienen las mismas soluciones.

La flecha apunta al campo que no tienes permitido asignar: balance. owner es pub, así que asignarlo está bien. Pero un struct literal es todo o nada. No puedes dejar fuera balance (Rust no rellena los campos que faltan), y tampoco puedes asignarlo. Así que, fuera de mod bank, sencillamente no hay forma de escribir Account { ... }.

Imagina que abres una cuenta en un banco de verdad. No escribes tú el saldo inicial en el formulario. Pones tu nombre, el banco crea la cuenta y el banco decide que empieza en cero. Account::new es ese formulario.

Los campos de un struct son privados por defecto, igual que las funciones y los módulos (mira Módulos). Si el código de fuera pudiera construir un Account con el saldo que quisiera, las reglas del módulo no servirían de nada. Alguien podría crear una cuenta con un millón, o con menos cincuenta. Como solo mod bank puede escribir el literal, todos los Account del programa los crea código que conoce las reglas.

Hay una segunda ventaja. Si más adelante añades un campo privado, como created_at o history, el código de fuera del módulo no se rompe, porque nunca escribió el literal. Llama a new, y tú actualizas new en un solo sitio.

Solución 1: Usa (o añade) un constructor pub fn new

Sección titulada «Solución 1: Usa (o añade) un constructor pub fn new»

Elige esta casi siempre. Primero busca una función new (o from_..., with_...); rustc a menudo te la señala. Si el struct es tuyo y no hay ninguna, escribe una:

mod bank {
pub struct Account {
pub owner: String,
balance: i64, // privado
}
impl Account {
// Todas las cuentas empiezan en 0. El dinero solo entra con 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!("{} tiene {}", account.owner, account.balance());
}

Salida:

Ferris tiene 250
▶ Pruébalo tú mismo

Dentro de mod bank el literal está permitido, así que new puede escribir Account { ... } sin problema. No olvides el pub del propio new; sin él te sale E0624 (associated function `new` is private, la función asociada new es privada). Si hay valores que deben rechazarse, haz que new devuelva Option<Account> o Result<Account, String>; la página de E0603 muestra uno. Más sobre constructores en Structs.

Solución 2: Haz los campos pub cuando los datos son simples

Sección titulada «Solución 2: Haz los campos pub cuando los datos son simples»

Elige esta cuando cualquier combinación de valores sirve. Un color son solo tres números; no hay ningún color “incorrecto” del que protegerse. Entonces un literal es la forma más clara de construirlo:

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);
}

Salida:

#f74c00
▶ Pruébalo tú mismo

Necesitas pub en todos los campos. Si solo tu propio crate debe construirlo, pub(crate) en los campos también funciona.

Cuándo dejar el campo privado: cuando un valor podría ser incorrecto (un saldo negativo, un porcentaje mayor que 100, un email sin @), o cuando dos campos tienen que coincidir entre sí (un total que debe ser la suma de items). Ahí un literal dejaría que la gente se saltara la comprobación, y lo correcto es la Solución 1.

Solución 3: Empieza desde Default y luego cambia los campos públicos

Sección titulada «Solución 3: Empieza desde Default y luego cambia los campos públicos»

Elige esta para structs de “ajustes” con valores por defecto razonables, donde unos campos son públicos y otros no. Primero, la trampa. La sintaxis de actualización de structs ..Default::default() parece que debería funcionar, pero no funciona:

mod settings {
pub struct Config {
pub verbose: bool,
retries: u32, // privado: debe estar entre 1 y 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

rustc te dice que el campo retries es privado, aunque tú nunca lo escribiste. ..Default::default() sigue siendo un struct literal: copia retries en el nuevo struct, y eso cuenta como asignarlo. En su lugar, crea primero el valor por defecto y cambia los campos públicos después:

mod settings {
pub struct Config {
pub verbose: bool,
retries: u32, // privado: debe estar entre 1 y 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; // los campos públicos se pueden cambiar después
println!("verbose: {}, retries: {}", config.verbose, config.retries());
}

Salida:

verbose: true, retries: 3
▶ Pruébalo tú mismo

Si todos los campos son pub, ..Default::default() funciona sin problema. Solo lo bloquean los campos privados.

Solución 4: Ofrece métodos de estilo builder

Sección titulada «Solución 4: Ofrece métodos de estilo builder»

Elige esta cuando hay varias opciones y algunas tienen reglas. Cada método recibe el struct, cambia un ajuste (comprobándolo) y te devuelve el struct, así que las llamadas se encadenan muy bien:

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
}
// Aquí vive la regla: de 1 a 10 reintentos.
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());
}

Salida:

verbose: true, retries: 10
▶ Pruébalo tú mismo

Pedir 50 reintentos se convirtió en 10 sin hacer ruido, porque la regla vive en un solo sitio. Muchos crates hacen esto, a veces con un tipo ConfigBuilder aparte y una llamada final a .build(). Mira Patrones de diseño para esa versión.

Después de mover un struct a un módulo. Todos los User { name, email } de main.rs funcionaban hasta que User se mudó a mod user (o a src/user.rs). Ahora fallan todos. Decide struct por struct: los datos simples llevan campos pub (Solución 2); todo lo que tenga reglas lleva un pub fn new (Solución 1).

Tipos de la biblioteca estándar o de un crate. No puedes escribir Vec { ... } ni Duration { ... }. rustc dice cannot construct `Vec<_, _>` with struct literal syntax due to private fields (no se puede construir Vec con un literal por sus campos privados) y te muestra funciones como Vec::new() y Vec::with_capacity(_). Usa esas, o Duration::from_secs(5).

Un struct #[non_exhaustive] de otro crate. Aquí ni siquiera los campos públicos ayudan, y el código es otro: error[E0639]: cannot create non-exhaustive struct using struct expression (no se puede crear un struct no exhaustivo con una expresión de struct). El crate quiere poder añadir campos más adelante sin romper tu código, así que usa su constructor o Default, exactamente como en la Solución 1 y la Solución 3.

  • E0616: el mismo campo privado, pero al leerlo (account.balance).
  • E0603: tuple struct constructor `X` is private, y funciones, structs o módulos privados.
  • E0624: associated function `new` is private. Tu constructor también necesita pub.
  • E0560: struct `X` has no field named `y` . Una errata en el nombre de un campo del literal.
  • E0063 (en inglés): missing field `x` in initializer of `Y` . Te dejaste fuera un campo público.
  • Todos los errores del compilador: la lista completa de páginas de errores.

Comprueba lo aprendido

1. pub struct Ticket { pub title: String, id: u32 } está en mod tickets. ¿Cómo puede main crear un Ticket?

2. ¿Por qué falla Config { verbose: true, ..Default::default() } cuando Config tiene un campo privado?

3. ¿Qué struct encaja bien con todos los campos pub y literales normales?

Puntuación: 0 / 3