Ir al contenido

Error E0609 de Rust: no field X on type T (campo inexistente)

E0609 significa: escribiste valor.algo, pero el tipo de valor no tiene ningún campo llamado algo. Casi siempre es un error al escribir el nombre, o el valor no es exactamente del tipo que crees (por ejemplo, un Option<User> en lugar de un User). El mensaje de error te dice el tipo exacto que ve Rust, y normalmente eso es todo lo que necesitas para arreglarlo. (Esta página forma parte de Errores del compilador de Rust explicados.)

error[E0609]: no field `emial` on type `User`
--> src/main.rs:12:25
|
12 | println!("{}", user.emial); // ❌ error de escritura: el campo es `email`
| ^^^^^ unknown field
|
help: a field with a similar name exists
|
12 - println!("{}", user.emial); // ❌ error de escritura: el campo es `email`
12 + println!("{}", user.email); // ❌ error de escritura: el campo es `email`
|

“No field emial on type User” quiere decir “no existe el campo emial en el tipo User”. El help te avisa de que existe un campo con un nombre parecido, y te muestra el cambio.

Este es el programa que lo produce:

struct User {
name: String,
email: String,
}
fn main() {
let user = User {
name: String::from("Ferris"),
email: String::from("[email protected]"),
};
println!("{}", user.name);
println!("{}", user.emial); // ❌ error de escritura: el campo es `email`
}

El titular tiene dos partes, y las dos importan:

  • no field `emial`: el nombre después del punto que Rust no encontró.
  • on type `User`: el tipo que Rust cree que tiene user.

Así que primero revisa el nombre, y después revisa el tipo. Si el tipo del mensaje no es el struct que esperabas, por ejemplo Option<User>, &Option<User>, Vec<User> o &T, el nombre está bien, y el problema es que primero tienes que sacar el struct de ahí.

Es como pedir en una tienda “el azul de la segunda estantería” cuando estás en la tienda equivocada. La petición está bien; simplemente estás en el sitio equivocado.

En Rust, los campos de cada struct quedan fijados cuando escribes el struct. El compilador conoce la lista completa y comprueba cada .campo en tiempo de compilación. No existe eso de “búscalo en tiempo de ejecución y obtén undefined” como en JavaScript, ni un AttributeError como en Python: un nombre de campo incorrecto nunca llega a tu programa. Mira Structs.

Los tipos envoltorio como Option<T> y Result<T, E> son tipos distintos: tu struct está dentro de ellos, pero ellos no tienen sus campos. Rust no los atraviesa por ti, porque puede que el valor ni siquiera esté. Tienes que decir qué pasa cuando falta. Mira Option.

El código genérico sigue la misma regla estricta. En fn greet<T>(x: &T), T podría ser cualquier tipo, incluidos i32 o bool, que no tienen campos. Rust comprueba una función genérica una sola vez para todos los T posibles, así que solo te deja usar lo que prometen tus trait bounds (restricciones de trait). Y los traits solo pueden prometer métodos, no campos. Mira Genéricos y Traits.

Elige esta cuando el tipo del mensaje es el struct que esperabas. Busca la línea help: a field with a similar name exists (existe un campo con un nombre parecido); rustc suele saber exactamente qué campo querías:

struct User {
name: String,
email: String,
}
fn main() {
let user = User {
name: String::from("Ferris"),
email: String::from("[email protected]"),
};
println!("{}", user.name);
println!("{}", user.email); // ✅ el nombre real del campo
}

Salida:

▶ Pruébalo tú mismo

Si no hay ninguna pista, salta a la definición del struct (en la mayoría de los editores: Ctrl+clic o F12 sobre el tipo) y copia el nombre de ahí. Los nombres de los campos distinguen mayúsculas y minúsculas: userName y user_name son campos distintos.

Solución 2: Sacar primero el struct del Option

Sección titulada «Solución 2: Sacar primero el struct del Option»

Elige esta cuando el mensaje dice que el tipo es Option<User>, Option<&User> o &Option<User>. Pasa constantemente con funciones que pueden no encontrar algo, y con métodos como .first(), .last(), .get() y .find():

error[E0609]: no field `name` on type `Option<User>`
--> src/main.rs:11:25
|
11 | println!("{}", user.name);
| ^^^^ unknown field
|
help: one of the expressions' fields has a field of the same name
|
11 | println!("{}", user.unwrap().name);
| +++++++++

Aquí el help dice que lo que hay dentro del Option sí tiene un campo con ese nombre, y propone .unwrap(). Esa pista compila, pero hace fallar el programa cuando el valor es None. Mejor: decide qué debe pasar en los dos casos, con if let, match, o map más un valor por defecto:

struct User {
name: String,
}
fn find_user(id: u32) -> Option<User> {
if id == 1 {
Some(User { name: String::from("Ferris") })
} else {
None
}
}
fn main() {
// if let: solo ejecuta el código cuando hay un User
if let Some(user) = find_user(1) {
println!("Encontrado: {}", user.name);
}
// match: maneja los dos casos
match find_user(2) {
Some(user) => println!("Encontrado: {}", user.name),
None => println!("No hay ningún usuario con id 2"),
}
// map + unwrap_or: toma un campo, o usa un valor por defecto
let users = vec![User { name: String::from("Ada") }];
let first_name = users.first().map(|u| u.name.as_str()).unwrap_or("nadie");
println!("Primer usuario: {first_name}");
}

Salida:

Encontrado: Ferris
No hay ningún usuario con id 2
Primer usuario: Ada
▶ Pruébalo tú mismo

Los mismos patrones funcionan con una referencia a un Option. Si maybe_user es un &Option<User>, escribe if let Some(user) = maybe_user y user será un &User. (Con &Option<User> rustc no da ninguna pista, así que merece la pena recordarlo.) Dentro de una función que devuelve Option, también funciona let user = find_user(1)?;. Mira Manejar un Option con match e if let.

Elige esta cuando el tipo es un tuple struct (struct de tupla), como struct Point(i32, i32);. Sus campos no tienen nombre, solo posición, así que p.x da no field `x` on type `Point` (con la pista p.0). Usa la posición, o desempaquétalo en variables con nombre. Si quieres nombres, conviértelo en un struct con campos con nombre:

struct Point(i32, i32); // tuple struct: los campos son .0 y .1
struct Size {
width: u32, // campos con nombre: usa .width y .height
height: u32,
}
fn main() {
let p = Point(3, 4);
println!("x = {}, y = {}", p.0, p.1);
let Point(x, y) = p; // o desempaquétalo en variables con nombre
println!("distancia al cuadrado = {}", x * x + y * y);
let screen = Size { width: 1920, height: 1080 };
println!("{}x{}", screen.width, screen.height);
}

Salida:

x = 3, y = 4
distancia al cuadrado = 25
1920x1080
▶ Pruébalo tú mismo

Las tuplas normales funcionan igual. pair.2 en una tupla de dos elementos da E0609 con la nota available fields are: `0`, `1` (los campos disponibles son 0 y 1). Mira Acceder a los elementos por posición.

Solución 4: Usar un método de trait en lugar de un campo en un T genérico

Sección titulada «Solución 4: Usar un método de trait en lugar de un campo en un T genérico»

Elige esta cuando el mensaje dice on type `&T` (o T), con la etiqueta type parameter 'T' declared here (aquí se declaró el parámetro de tipo T). Un código como este falla, aunque todos los tipos que le pases tengan un campo name:

fn greet<T>(animal: &T) {
println!("Hello, {}!", animal.name); // ❌ no field `name` on type `&T`
}

Los traits no pueden exigir campos, pero sí pueden exigir un método getter (de lectura). Define un trait pequeño, impleméntalo para cada tipo y añádelo como bound de T:

trait Named {
fn name(&self) -> &str;
}
struct Dog {
name: String,
}
struct Robot {
model: String,
}
impl Named for Dog {
fn name(&self) -> &str {
&self.name
}
}
impl Named for Robot {
fn name(&self) -> &str {
&self.model // el campo ni siquiera tiene que llamarse igual
}
}
fn greet<T: Named>(thing: &T) {
println!("¡Hola, {}!", thing.name()); // una llamada a un método, no un campo
}
fn main() {
greet(&Dog { name: String::from("Rex") });
greet(&Robot { model: String::from("R2-D2") });
}

Salida:

¡Hola, Rex!
¡Hola, R2-D2!
▶ Pruébalo tú mismo

Si la función solo trabaja con un tipo, no necesitas un genérico: escribe fn greet(animal: &Dog) y animal.name funciona. Mira Traits como parámetros de funciones.

Un método sin (). Escribir word.len o cart.total (cuando total es un método) se parece mucho a E0609, pero rustc da otro código: E0615, “attempted to take value of method” (intentaste tomar el valor de un método), con la pista “use parentheses to call the method” (usa paréntesis para llamar al método). En un Vec, scores.len da E0616, “field len of struct Vec is private” (el campo len de Vec es privado), porque Vec de verdad tiene un campo oculto con ese nombre. La solución es la misma para los dos: word.len(), scores.len(), cart.total(). Y "hello".length es un E0609 normal sobre &str: en Rust se llama len(). Mira Strings.

Un Vec en lugar de un elemento. dogs.name en un Vec<Dog> da no field `name` on type `Vec<Dog>`. Una lista no tiene name; cada elemento sí. Solución: elige uno con dogs[0].name, o recórrelos con for dog in &dogs { println!("{}", dog.name); }. Mira Vectores.

Un Result en lugar del valor. load_config().port da no field `port` on type `Result<Config, String>`, con la misma pista de .unwrap(). Solución: let config = load_config()?; dentro de una función que devuelve Result, o un match. Mira El operador ?.

Un campo en un número. age.value en un u32 es otro código distinto: E0610, “u32 is a primitive type and therefore doesn’t have fields” (u32 es un tipo primitivo y por eso no tiene campos). Los números, bool y char no tienen ningún campo; seguramente querías usar una variable de tipo struct.

Comprueba lo aprendido

1. El error dice que no existe el campo name en el tipo Option<User>. ¿Qué te está diciendo?

2. ¿Cómo lees el primer campo de struct Meters(f64); desde un valor m?

3. En fn show<T>(x: &T), ¿cómo puedes leer un nombre de cualquier T?

Puntuación: 0 / 3