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.
El mensaje de error
Sección titulada «El mensaje de error»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, // ❌ 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.
Qué significa
Sección titulada «Qué significa»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.
Por qué Rust hace esto
Sección titulada «Por qué Rust hace esto»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.
Cómo solucionarlo
Sección titulada «Cómo solucionarlo»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 250Dentro 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:
#f74c00Necesitas 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 privaterustc 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: 3Si 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: 10Pedir 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.
Dónde lo verás normalmente
Sección titulada «Dónde lo verás normalmente»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.
Errores relacionados
Sección titulada «Errores relacionados»- 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 necesitapub. - 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 que aprendiste
Sección titulada «Comprueba lo que aprendiste»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
Para leer más
Sección titulada «Para leer más»- E0451 — Rust error codes index (en inglés): la explicación oficial, con un ejemplo mínimo.
- Making Structs and Enums Public — El libro de Rust (en inglés): un struct con un campo privado que necesita un constructor público. También existe una traducción al español de la comunidad del libro.
- Struct visibility — Rust by Example (en inglés): cómo construir structs con campos públicos y privados desde fuera de un módulo.
- Builder pattern — Rust Design Patterns (en inglés): el patrón builder completo, con un tipo builder aparte.
- El trait
Default— documentación de la biblioteca estándar (en inglés): cómo derivar e implementar valores por defecto.