Ir al contenido

Error E0308 de Rust: mismatched types (tipos que no coinciden)

El error E0308 significa que Rust esperaba un valor de un tipo, pero le diste un valor de otro tipo distinto. Seguramente es el error que más vas a ver en Rust, y el mensaje casi siempre te dice los dos tipos, así que normalmente se arregla rápido. (¿Buscas otro error? Mira la lista de errores del compilador).

error[E0308]: mismatched types
--> src/main.rs:6:29
|
6 | let user = User { name: "Ferris" };
| ^^^^^^^^ expected `String`, found `&str`
|
help: try using a conversion method
|
6 | let user = User { name: "Ferris".to_string() };
| ++++++++++++

El compilador dice “tipos que no coinciden”: esperaba un String y encontró un &str. Y en la parte help: te propone un método de conversión.

Este es el programa que lo provoca:

struct User {
name: String,
}
fn main() {
let user = User { name: "Ferris" };
println!("{}", user.name);
}

Todos los mensajes E0308 tienen la misma forma: expected A, found B (se esperaba A, se encontró B).

  • Expected es el tipo que Rust necesita en ese sitio. Viene de otra parte del código: una anotación de tipo, el parámetro o el tipo de retorno de una función, el campo de una struct u otra rama de un if/match.
  • Found es el tipo del valor que realmente escribiste ahí.

En el ejemplo, el campo name se declaró como String, así que eso es lo que Rust espera. Pero "Ferris" es un literal de cadena, y su tipo es &str. Están relacionados, pero no son el mismo tipo.

Fíjate también en las líneas help:. Con E0308, el compilador a menudo te sugiere la solución exacta, como el .to_string() de arriba.

Rust nunca convierte entre tipos sin avisar. En algunos lenguajes, un número se convierte en texto sin que te des cuenta, o un número grande se recorta para caber en uno más pequeño. Esas conversiones ocultas causan bugs reales: datos perdidos, resultados incorrectos y fallos que aparecen lejos del error de verdad.

Por eso Rust hace que cada conversión sea visible en el código. Escribes un poco más, pero cuando vuelves a leer el código ves exactamente dónde cambia de tipo un valor y si ese paso puede fallar. Los tipos en sí los tienes en Tipos de datos.

Cada solución empieza con la forma del error, para que saltes directamente a la que se parece a la tuya.

Solución 1: Quita el punto y coma del final

Sección titulada «Solución 1: Quita el punto y coma del final»
error[E0308]: mismatched types
--> src/main.rs:1:46
|
1 | fn total_price(price: u32, quantity: u32) -> u32 {
| ----------- ^^^ expected `u32`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression
2 | price * quantity;
| - help: remove this semicolon to return this value

Aquí el compilador dice que la función devuelve () sin querer, porque su cuerpo no termina en una expresión ni en un return, y te sugiere quitar el punto y coma.

Si ves found (), revisa la última línea de la función. En Rust, la última expresión de un bloque sin punto y coma es su valor de retorno. Al añadir ; la conviertes en una instrucción, y la función devuelve () (el tipo “nada”). Borra el punto y coma:

fn total_price(price: u32, quantity: u32) -> u32 {
price * quantity // sin punto y coma: se devuelve este valor
}
fn main() {
println!("Total: {}", total_price(250, 3));
}

Salida:

Total: 750
▶ Pruébalo tú mismo

Lo mismo pasa dentro de los bloques if y de las ramas de match. Más detalles en Funciones.

String es una cadena de texto con dueño que puede crecer. &str es una vista prestada de un texto. La historia completa está en Strings. La conversión es corta en los dos sentidos:

  • De &str a String: .to_string() o String::from(...).
  • De String a &str: préstalo con & (o llama a .as_str()).

El segundo caso aparece como expected &str, found String, normalmente cuando pasas un String a una función que recibe &str.

struct User {
name: String,
}
fn greet(name: &str) {
println!("¡Hola, {name}!");
}
fn main() {
// &str -> String: .to_string() o String::from()
let user = User { name: "Ferris".to_string() };
// String -> &str: préstalo con & (o llama a .as_str())
greet(&user.name);
greet(user.name.as_str());
let mut tags: Vec<String> = Vec::new();
tags.push(String::from("rust"));
println!("{tags:?}");
}

Salida:

¡Hola, Ferris!
¡Hola, Ferris!
["rust"]
▶ Pruébalo tú mismo

Solución 3: Convierte entre tipos numéricos

Sección titulada «Solución 3: Convierte entre tipos numéricos»
error[E0308]: mismatched types
--> src/main.rs:3:22
|
3 | let count: i32 = names.len();
| --- ^^^^^^^^^^^ expected `i32`, found `usize`
| |
| expected due to this

El compilador esperaba un i32 (por la anotación, “expected due to this”) y encontró un usize.

i32, i64, u32 y usize son tipos distintos, y Rust no los mezcla. Las longitudes y los índices siempre son usize. Elige una conversión:

  • .into() (o i64::from(x)) cuando la conversión nunca puede perder datos, como de i32 a i64.
  • i64::try_from(x) (o .try_into()) cuando el valor podría no caber. Devuelve un Result, así que tú decides qué pasa si falla.
  • as es la conversión rápida. Nunca falla, pero recorta sin avisar los valores que no caben, así que úsala solo cuando sepas que el valor es pequeño.
fn average(total: i64, count: i64) -> i64 {
total / count
}
fn main() {
let scores: Vec<i32> = vec![80, 95, 70];
let total: i32 = scores.iter().sum();
// i32 -> i64 siempre cabe, así que basta con .into()
let total: i64 = total.into();
// usize -> i64 podría no caber, así que try_from lo comprueba antes
let count = i64::try_from(scores.len()).unwrap();
println!("Media: {}", average(total, count));
// `as` también funciona, pero recorta sin avisar los valores que no caben
let players = scores.len() as i32;
println!("{players} jugadores");
}

Salida:

Media: 81
3 jugadores
▶ Pruébalo tú mismo

Muchas veces lo más sencillo es cambiar la anotación: let count: usize = names.len();. Las páginas de Conversión de tipos y Conversiones profundizan más.

Solución 4: Envuelve en Some, o saca el valor del Option

Sección titulada «Solución 4: Envuelve en Some, o saca el valor del Option»
error[E0308]: mismatched types
--> src/main.rs:3:27
|
3 | let position: usize = names.iter().position(|n| *n == "Ben");
| ----- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `usize`, found `Option<usize>`
| |
| expected due to this

Aquí Rust esperaba un usize y encontró un Option<usize>.

Option<usize> no es un usize. Es “quizá un usize, quizá nada”. Este desajuste puede ir en los dos sentidos:

  • Expected T, found Option<T>: tienes que ocuparte del caso “nada”, con match, if let o unwrap_or(valor_por_defecto).
  • Expected Option<T>, found T: tu función devuelve Option, así que envuelve el valor: Some(10) en lugar de 10.
fn find_discount(code: &str) -> Option<u32> {
if code == "SAVE10" {
return Some(10); // T -> Option<T>: envuélvelo en Some
}
None
}
fn main() {
let names = vec!["Ana", "Ben", "Cy"];
// Option<T> -> T: ocúpate del caso "no se encontró nada"...
match names.iter().position(|n| *n == "Ben") {
Some(i) => println!("Ben está en la posición {i}"),
None => println!("Ben no está aquí"),
}
// ...o elige un valor por defecto con unwrap_or
let discount: u32 = find_discount("HELLO").unwrap_or(0);
println!("Descuento: {discount}%");
}

Salida:

Ben está en la posición 1
Descuento: 0%
▶ Pruébalo tú mismo

Puede que el compilador te sugiera .expect("REASON"). También funciona, pero el programa se detiene con un panic cuando el valor es None, así que úsalo solo cuando None de verdad no pueda ocurrir. Mira Option.

Solución 5: Haz que todas las ramas de if/else y match tengan el mismo tipo

Sección titulada «Solución 5: Haz que todas las ramas de if/else y match tengan el mismo tipo»
error[E0308]: `if` and `else` have incompatible types
--> src/main.rs:3:52
|
3 | let label = if stock > 0 { "in stock" } else { stock };
| ---------- ^^^^^ expected `&str`, found integer
| |
| expected because of this

El mensaje dice que if y else tienen tipos incompatibles: la primera rama da un &str y la segunda un entero.

Cuando un if o un match produce un valor, Rust necesita saber su único tipo. Así que todas las ramas deben producir el mismo tipo. La primera rama marca lo que se espera, y el error señala la rama que no encaja. Con match, el mensaje dice “match arms have incompatible types”. Haz que la rama rara coincida con las demás:

fn status_text(code: u16) -> String {
match code {
200 => "OK".to_string(),
404 => "No encontrado".to_string(),
_ => format!("Código desconocido {code}"), // todas las ramas son String
}
}
fn main() {
let stock = 0;
let label = if stock > 0 { "disponible" } else { "agotado" }; // las dos son &str
println!("{label}");
println!("{}", status_text(404));
println!("{}", status_text(418));
}

Salida:

agotado
No encontrado
Código desconocido 418
▶ Pruébalo tú mismo

Aquí una rama necesitaba format!, que crea un String, así que las otras ramas también pasaron a ser String. Mira If…Else y Match.

  • Al leer lo que escribe el usuario. let age: u32 = input.trim(); da “expected u32, found &str”. Un texto no es un número. Solución: conviértelo con parse: let age: u32 = input.trim().parse().unwrap(); (o maneja el error, mira Result).
  • Al meter literales en un Vec<String>. tags.push("rust") da “expected String, found &str”. Solución: tags.push("rust".to_string()), o usa un Vec<&str> si todas las cadenas son literales.
  • Al hacer cuentas con números de distinto tipo. total + n, con total de tipo i64 y n de tipo i32, da E0308 (y a menudo E0277 al lado). Solución: convierte primero uno de los lados: total + i64::from(n).

Comprueba lo aprendido

1. Una función dice -> i32, pero el error dice "expected i32, found ()". ¿Cuál es la causa más probable?

2. Una función recibe &str y tú tienes let name = String::from("Ana");. ¿Cómo se lo pasas?

3. ¿Qué conversión nunca puede perder datos sin avisar?

Puntuación: 0 / 3