Ошибка Rust E0308: mismatched types (несовпадение типов)
Ошибка E0308 значит, что Rust ожидал значение одного типа, а ты дал ему значение другого типа. Скорее всего, это ошибка, которую ты будешь видеть в Rust чаще всех, но в сообщении почти всегда указаны оба типа, так что исправляется она обычно быстро. (Ищешь другую ошибку? Загляни в обзор ошибок компилятора.)
Сообщение об ошибке
Заголовок раздела «Сообщение об ошибке»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() }; | ++++++++++++«Mismatched types» значит «несовпадение типов»: ожидался String, а найден &str. Подсказка help предлагает метод преобразования.
Вот программа, которая выдаёт эту ошибку:
struct User { name: String,}
fn main() { let user = User { name: "Ferris" }; println!("{}", user.name);}Что это значит
Заголовок раздела «Что это значит»У каждого сообщения E0308 одна и та же форма: expected A, found B («ожидался A, найден B»).
- Expected — тип, который Rust нужен в этом месте. Он берётся откуда-то ещё: из аннотации типа, параметра или возвращаемого типа функции, поля структуры или другой ветки
if/match. - Found — тип значения, которое ты там на самом деле написал.
В примере поле name объявлено как String, поэтому Rust ожидает именно его. Но "Ferris" — строковый литерал, у него тип &str. Эти типы связаны, но это не один и тот же тип.
Смотри и на строки help:. Для E0308 компилятор часто предлагает точное исправление, как .to_string() выше.
Почему Rust так делает
Заголовок раздела «Почему Rust так делает»Rust никогда не преобразует типы молча. В некоторых языках число тихо превращается в строку, а большое число тихо обрезается, чтобы влезть в меньший тип. Такие скрытые преобразования вызывают настоящие баги: потерю данных, неверные результаты, падения далеко от места настоящей ошибки.
Поэтому Rust делает каждое преобразование видимым в коде. Печатать приходится чуть больше, зато, читая код потом, ты точно видишь, где значение меняет тип и может ли этот шаг не сработать. Сами типы описаны в уроке Типы данных.
Как исправить
Заголовок раздела «Как исправить»Каждое исправление ниже начинается с формы ошибки, так что можно сразу перейти к тому, что похоже на твой случай.
Исправление 1: убери точку с запятой в конце
Заголовок раздела «Исправление 1: убери точку с запятой в конце»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` expression2 | price * quantity; | - help: remove this semicolon to return this valueЕсли видишь found (), посмотри на последнюю строку функции. В Rust последнее выражение блока без точки с запятой — это его возвращаемое значение. Точка с запятой превращает его в инструкцию, и функция возвращает () (тип «ничего»). Компилятор так и пишет: функция неявно возвращает (), потому что у её тела нет завершающего выражения или return, — и подсказывает убрать точку с запятой:
fn total_price(price: u32, quantity: u32) -> u32 { price * quantity // без точки с запятой: это значение возвращается}
fn main() { println!("Итого: {}", total_price(250, 3));}Вывод:
Итого: 750То же самое бывает внутри блоков if и веток match. Подробнее — в уроке Функции в Rust.
Исправление 2: преобразуй String в &str и обратно
Заголовок раздела «Исправление 2: преобразуй String в &str и обратно»String — владеющая строка, которая может расти. &str — заимствованный взгляд на какой-то текст. Вся история — в уроке Строки. Преобразование в обе стороны короткое:
&strвString:.to_string()илиString::from(...).Stringв&str: заимствуй через&(или вызови.as_str()).
Второй случай выглядит как expected &str, found String — обычно когда передаёшь String в функцию, которая принимает &str.
struct User { name: String,}
fn greet(name: &str) { println!("Привет, {name}!");}
fn main() { // &str -> String: .to_string() или String::from() let user = User { name: "Ferris".to_string() };
// String -> &str: заимствуем через & (или вызываем .as_str()) greet(&user.name); greet(user.name.as_str());
let mut tags: Vec<String> = Vec::new(); tags.push(String::from("rust")); println!("{tags:?}");}Вывод:
Привет, Ferris!Привет, Ferris!["rust"]Исправление 3: преобразуй один числовой тип в другой
Заголовок раздела «Исправление 3: преобразуй один числовой тип в другой»error[E0308]: mismatched types --> src/main.rs:3:22 |3 | let count: i32 = names.len(); | --- ^^^^^^^^^^^ expected `i32`, found `usize` | | | expected due to thisi32, i64, u32 и usize — разные типы, и Rust их не смешивает. Длины и индексы всегда имеют тип usize. Выбери преобразование:
.into()(илиi64::from(x)), когда преобразование никогда не теряет данные, напримерi32вi64.i64::try_from(x)(или.try_into()), когда значение может не поместиться. ВозвращаетResult, так что ты сам решаешь, что делать при неудаче.as— быстрое приведение. Оно никогда не падает, но молча обрезает значения, которые не помещаются, так что используй его, только если точно знаешь, что значение маленькое.
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 всегда помещается, поэтому хватает .into() let total: i64 = total.into();
// usize -> i64 может не поместиться, поэтому try_from сначала проверяет let count = i64::try_from(scores.len()).unwrap();
println!("Среднее: {}", average(total, count));
// `as` тоже работает, но молча обрезает значения, которые не помещаются let players = scores.len() as i32; println!("Игроков: {players}");}Вывод:
Среднее: 81Игроков: 3Часто проще всего поменять аннотацию: let count: usize = names.len();. Глубже эта тема разобрана в уроках Приведение типов и Преобразования (From, Into).
Исправление 4: оберни в Some или достань значение из Option
Заголовок раздела «Исправление 4: оберни в Some или достань значение из 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 thisOption<usize> — это не usize. Это «может быть, usize, а может быть, ничего». Такое несовпадение бывает в обе стороны:
- Expected
T, foundOption<T>: нужно обработать случай «ничего» черезmatch,if letилиunwrap_or(default). - Expected
Option<T>, foundT: функция возвращаетOption, так что оберни значение:Some(10)вместо10.
fn find_discount(code: &str) -> Option<u32> { if code == "SAVE10" { return Some(10); // T -> Option<T>: оборачиваем в Some } None}
fn main() { let names = vec!["Ana", "Ben", "Cy"];
// Option<T> -> T: обрабатываем случай «ничего не нашлось»... match names.iter().position(|n| *n == "Ben") { Some(i) => println!("Ben на позиции {i}"), None => println!("Ben здесь нет"), }
// ...или выбираем значение по умолчанию через unwrap_or let discount: u32 = find_discount("HELLO").unwrap_or(0); println!("Скидка: {discount}%");}Вывод:
Ben на позиции 1Скидка: 0%Компилятор может предложить .expect("REASON"). Это тоже работает, но программа упадёт, если значение окажется None, так что используй его, только когда None действительно невозможен. См. Option.
Исправление 5: пусть все ветки if/else и match имеют один тип
Заголовок раздела «Исправление 5: пусть все ветки if/else и 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Когда if или match выдают значение, Rust должен знать его единственный тип. Поэтому каждая ветка должна давать один и тот же тип. Первая ветка задаёт ожидание, а ошибка указывает на ветку, которая с ним не согласна. Для match сообщение звучит как «match arms have incompatible types» («у веток match несовместимые типы»). Приведи «выбивающуюся» ветку к общему типу:
fn status_text(code: u16) -> String { match code { 200 => "OK".to_string(), 404 => "Not Found".to_string(), _ => format!("Неизвестный код {code}"), // каждая ветка — String }}
fn main() { let stock = 0; let label = if stock > 0 { "в наличии" } else { "распродано" }; // обе &str println!("{label}");
println!("{}", status_text(404)); println!("{}", status_text(418));}Вывод:
распроданоNot FoundНеизвестный код 418Здесь одной ветке нужен был format!, который создаёт String, поэтому и остальные ветки стали String. См. If…else и Match.
Где это обычно встречается
Заголовок раздела «Где это обычно встречается»- Чтение ввода пользователя.
let age: u32 = input.trim();даёт «expectedu32, found&str». Текст — это не число. Исправление: разбери его:let age: u32 = input.trim().parse().unwrap();(или обработай ошибку, см. Result). - Литералы в
Vec<String>.tags.push("rust")даёт «expectedString, found&str». Исправление:tags.push("rust".to_string())или сделайVec<&str>, если все строки — литералы. - Арифметика с разными числовыми типами.
total + n, гдеtotal—i64, аn—i32, даёт E0308 (и часто рядом E0277). Исправление: сначала преобразуй одну сторону:total + i64::from(n).
Связанные ошибки
Заголовок раздела «Связанные ошибки»- E0277: trait bound not satisfied: часто появляется рядом с E0308, например когда складываешь
i64иi32. - E0599: no method found: ты вызвал метод, которого у этого типа нет, — часто потому, что у значения другой тип, чем ты думал.
- E0425: cannot find value in this scope: имя, которого Rust вообще не знает.
- Все ошибки компилятора: полный список страниц об ошибках.
Проверь себя
Заголовок раздела «Проверь себя»Проверь себя
1. У функции написано -> i32, а ошибка говорит "expected i32, found ()". Какая причина вероятнее всего?
2. Функция принимает &str, а у тебя let name = String::from("Ana");. Как её передать?
3. Какое преобразование никогда не теряет данные молча?
Результат: 0 / 3
Что почитать
Заголовок раздела «Что почитать»- E0308 — Rust error codes index (на английском): официальное объяснение этой ошибки.
- Функции с возвращаемыми значениями — книга «Язык программирования Rust» (перевод на русский): почему точка с запятой в конце меняет то, что возвращает функция.
- Хранение закодированного текста UTF-8 со строками — книга «Язык программирования Rust» (перевод на русский): как связаны
Stringи&strи как преобразовывать одно в другое. - Преобразования — Rust на примерах (перевод на русский):
From,Into,TryFromиTryIntoна коротких примерах. - Option — стандартная библиотека Rust (на английском): все методы, чтобы достать значение из
Option, напримерunwrap_orиmap.