Aller au contenu

Erreur Rust E0308 : mismatched types (types incompatibles)

L’erreur E0308 veut dire que Rust attendait une valeur d’un certain type, mais que vous lui avez donné une valeur d’un autre type. C’est sans doute l’erreur que vous verrez le plus souvent en Rust. Heureusement, le message indique presque toujours les deux types, donc elle se corrige en général très vite. (Vous cherchez une autre erreur ? Consultez la liste des erreurs du compilateur.)

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

Le compilateur dit : « types incompatibles : String attendu, &str trouvé », et il propose une méthode de conversion.

Voici le programme qui la provoque :

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

Tous les messages E0308 ont la même forme : expected A, found B (« A attendu, B trouvé »).

  • Expected (attendu), c’est le type dont Rust a besoin à cet endroit. Il vient d’ailleurs : une annotation de type, le paramètre ou le type de retour d’une fonction, un champ de struct, ou une autre branche d’un if/match.
  • Found (trouvé), c’est le type de la valeur que vous avez réellement écrite là.

Dans l’exemple, le champ name a été déclaré comme String, donc c’est ce que Rust attend. Mais "Ferris" est une chaîne littérale, dont le type est &str. Les deux sont liés, mais ce ne sont pas le même type.

Regardez aussi les lignes help:. Pour E0308, le compilateur propose souvent la correction exacte, comme .to_string() ci-dessus.

Rust ne convertit jamais un type en un autre en silence. Dans certains langages, un nombre devient discrètement une chaîne, ou un grand nombre est tronqué pour rentrer dans un type plus petit. Ces conversions cachées provoquent de vrais bugs : des données perdues, des résultats faux, des plantages très loin de l’erreur d’origine.

Rust rend donc chaque conversion visible dans le code. Ça demande un peu plus de frappe, mais quand vous relisez le code plus tard, vous voyez exactement où une valeur change de type et si cette étape peut échouer. Les types eux-mêmes sont présentés dans Les types de données.

Chaque solution ci-dessous commence par la forme de l’erreur : allez directement à celle qui ressemble à la vôtre.

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

Si vous voyez found (), regardez la dernière ligne de la fonction. En Rust, la dernière expression d’un bloc sans point-virgule est sa valeur de retour. Ajouter ; la transforme en instruction, et la fonction renvoie alors () (le type « rien ») à la place. Le compilateur le dit d’ailleurs : « supprimez ce point-virgule pour renvoyer cette valeur ». Effacez-le :

fn total_price(price: u32, quantity: u32) -> u32 {
price * quantity // pas de point-virgule : cette valeur est renvoyée
}
fn main() {
println!("Total : {}", total_price(250, 3));
}

Sortie :

Total : 750
▶ Essayez par vous-même

La même chose arrive dans les blocs if et les branches de match. Plus de détails dans Les fonctions.

String est une chaîne qui vous appartient et qui peut grandir. &str est une vue empruntée sur un texte. Tous les détails sont dans Les chaînes de caractères. La conversion est courte dans les deux sens :

  • de &str vers String : .to_string() ou String::from(...) ;
  • de String vers &str : empruntez-la avec & (ou appelez .as_str()).

Le deuxième cas apparaît sous la forme expected &str, found String, en général quand vous passez une String à une fonction qui prend un &str.

struct User {
name: String,
}
fn greet(name: &str) {
println!("Bonjour, {name} !");
}
fn main() {
// &str -> String : .to_string() ou String::from()
let user = User { name: "Ferris".to_string() };
// String -> &str : empruntez-la avec & (ou appelez .as_str())
greet(&user.name);
greet(user.name.as_str());
let mut tags: Vec<String> = Vec::new();
tags.push(String::from("rust"));
println!("{tags:?}");
}

Sortie :

Bonjour, Ferris !
Bonjour, Ferris !
["rust"]
▶ Essayez par vous-même
error[E0308]: mismatched types
--> src/main.rs:3:22
|
3 | let count: i32 = names.len();
| --- ^^^^^^^^^^^ expected `i32`, found `usize`
| |
| expected due to this

i32, i64, u32 et usize sont tous des types différents, et Rust ne les mélange pas. Les longueurs et les indices sont toujours des usize. Choisissez une conversion :

  • .into() (ou i64::from(x)) quand la conversion ne peut jamais perdre de données, par exemple de i32 vers i64.
  • i64::try_from(x) (ou .try_into()) quand la valeur risque de ne pas rentrer. Elle renvoie un Result, et c’est vous qui décidez quoi faire en cas d’échec.
  • as est la conversion rapide. Elle n’échoue jamais, mais elle tronque en silence les valeurs trop grandes : utilisez-la seulement quand vous savez que la valeur est petite.
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 rentre toujours, donc .into() suffit
let total: i64 = total.into();
// usize -> i64 pourrait ne pas rentrer, donc try_from vérifie d'abord
let count = i64::try_from(scores.len()).unwrap();
println!("Moyenne : {}", average(total, count));
// `as` marche aussi, mais tronque en silence les valeurs trop grandes
let players = scores.len() as i32;
println!("{players} joueurs");
}

Sortie :

Moyenne : 81
3 joueurs
▶ Essayez par vous-même

Souvent, la correction la plus simple est de changer l’annotation : let count: usize = names.len();. Les pages Conversion de types et Les conversions vont plus loin.

Solution 4 : envelopper dans Some, ou sortir la valeur de l’Option

Section intitulée « Solution 4 : envelopper dans Some, ou sortir la valeur de l’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

Une Option<usize> n’est pas un usize. C’est « peut-être un usize, peut-être rien ». Cette erreur existe dans les deux sens :

  • Expected T, found Option<T> : vous devez traiter le cas « rien », avec match, if let ou unwrap_or(valeur_par_défaut).
  • Expected Option<T>, found T : votre fonction renvoie une Option, donc enveloppez la valeur : Some(10) au lieu de 10.
fn find_discount(code: &str) -> Option<u32> {
if code == "SAVE10" {
return Some(10); // T -> Option<T> : on l'enveloppe dans Some
}
None
}
fn main() {
let names = vec!["Ana", "Ben", "Cy"];
// Option<T> -> T : on traite le cas « rien trouvé »...
match names.iter().position(|n| *n == "Ben") {
Some(i) => println!("Ben est à la position {i}"),
None => println!("Ben n'est pas là"),
}
// ...ou on choisit une valeur par défaut avec unwrap_or
let discount: u32 = find_discount("HELLO").unwrap_or(0);
println!("Réduction : {discount} %");
}

Sortie :

Ben est à la position 1
Réduction : 0 %
▶ Essayez par vous-même

Le compilateur peut vous proposer .expect("REASON"). Ça marche aussi, mais le programme plante si la valeur est None : ne l’utilisez que quand None est vraiment impossible. Voir Option.

Solution 5 : donner le même type à toutes les branches d’un if/else ou d’un match

Section intitulée « Solution 5 : donner le même type à toutes les branches d’un if/else ou d’un match »
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

Ici, le message dit que « if et else ont des types incompatibles ». Quand un if ou un match produit une valeur, Rust doit connaître son type, et il n’y en a qu’un. Toutes les branches doivent donc produire le même type. La première branche fixe ce qui est attendu, et l’erreur montre la branche qui n’est pas d’accord. Pour match, le message dit « match arms have incompatible types ». Mettez la branche fautive au diapason :

fn status_text(code: u16) -> String {
match code {
200 => "OK".to_string(),
404 => "Not Found".to_string(),
_ => format!("Code inconnu {code}"), // chaque branche est une String
}
}
fn main() {
let stock = 0;
let label = if stock > 0 { "en stock" } else { "épuisé" }; // deux &str
println!("{label}");
println!("{}", status_text(404));
println!("{}", status_text(418));
}

Sortie :

épuisé
Not Found
Code inconnu 418
▶ Essayez par vous-même

Ici, une branche avait besoin de format!, qui crée une String, donc les autres branches sont devenues des String elles aussi. (« OK » et « Not Found » restent en anglais : ce sont les libellés standard des codes HTTP.) Voir if/else et match.

  • En lisant une saisie de l’utilisateur. let age: u32 = input.trim(); donne « expected u32, found &str ». Du texte n’est pas un nombre. Correction : convertissez-le : let age: u32 = input.trim().parse().unwrap(); (ou gérez l’erreur, voir Result).
  • En ajoutant des littéraux dans un Vec<String>. tags.push("rust") donne « expected String, found &str ». Correction : tags.push("rust".to_string()), ou utilisez un Vec<&str> si toutes les chaînes sont des littéraux.
  • En calculant avec des types de nombres différents. total + n où total est un i64 et n un i32 donne E0308 (et souvent E0277 juste à côté). Correction : convertissez d’abord un des deux côtés : total + i64::from(n).

Petit quiz

1. Une fonction indique -> i32, mais l'erreur dit "expected i32, found ()". Quelle est la cause la plus probable ?

2. Une fonction prend un &str et vous avez let name = String::from("Ana");. Comment la lui passer ?

3. Quelle conversion ne peut jamais perdre de données en silence ?

Score : 0 / 3