Как сравнить String со строковыми литералами в match в Rust?
Правильный способ: сравнение String с &str через match
Проблема в том, что String и &str — это разные типы. В Rust match требует, чтобы все ветки имели один и тот же тип, поэтому напрямую сравнить String с литералами &str не получится. Однако есть несколько простых и идиоматичных решений.
Решение 1: Преобразование String в &str через ссылку
Самый распространённый способ — взять ссылку на String, которая автоматически становится &str благодаря Deref-приведению (Deref coercion):
fn main() {
let user_input = String::from("start");
match &user_input[..] { // или просто &user_input
"start" => println!("Запуск..."),
"stop" => println!("Остановка..."),
"help" => println!("Помощь..."),
_ => println!("Неизвестная команда"),
}
}
Здесь &user_input или &user_input[..] даёт &str, который уже можно сравнивать с литералами. Это нулевая стоимость — просто заимствование.
Решение 2: Использование метода as_str()
Явный вызов as_str() делает то же самое:
match user_input.as_str() {
"start" => println!("Запуск..."),
"stop" => println!("Остановка..."),
"help" => println!("Помощь..."),
_ => println!("Неизвестная команда"),
}
Этот вариант более читаемый для новичков, но по сути идентичен первому.
Решение 3: Преобразование литералов в String (не рекомендуется)
Можно создать String из каждого литерала, но это плохая практика — каждое создание String выделяет память в куче:
match user_input {
s if s == "start" => println!("Запуск..."),
s if s == "stop" => println!("Остановка..."),
// ... лучше не делайте так
}
Что выбрать?
- Для простых случаев — используйте
user_input.as_str()или&user_input. Это идиоматично и эффективно. - Для частых сравнений — рассмотрите перечисление (
enum), если строки фиксированы:
#[derive(Debug)]
enum Command {
Start,
Stop,
Help,
Unknown,
}
impl From<&str> for Command {
fn from(s: &str) -> Self {
match s {
"start" => Command::Start,
"stop" => Command::Stop,
"help" => Command::Help,
_ => Command::Unknown,
}
}
}
fn main() {
let user_input = String::from("start");
let cmd: Command = user_input.as_str().into();
println!("{:?}", cmd);
}
Такой подход даёт безопасность типов и производительность, особенно если команд много.
Важное замечание про производительность
Сравнение String с &str через match — это O(n) по длине строки. Для большинства приложений это нормально. Если у вас тысячи вариантов, лучше использовать HashMap или match с enum.
Итог: используйте match user_input.as_str() или match &user_input[..]. Это просто, эффективно и соответствует идиомам Rust.
Альтернативный подход: использование макроса matches!
Если вам не нужны разные действия для каждой ветки, а только проверка, принадлежит ли строка набору значений, используйте макрос matches!:
fn main() {
let user_input = String::from("start");
if matches!(user_input.as_str(), "start" | "stop" | "help") {
println!("Известная команда");
} else {
println!("Неизвестная команда");
}
}
Макрос matches! принимает выражение и шаблон. Он возвращает bool, что удобно для простых проверок. Недостаток — нельзя выполнять разные действия для разных вариантов.
Для сложной логики: комбинация match и замыканий
Если веток много и логика разная, но вы хотите избежать повторного вызова as_str(), можно сохранить ссылку:
fn handle_command(input: &str) {
match input {
"start" => println!("Запуск..."),
"stop" => println!("Остановка..."),
_ => println!("Неизвестно"),
}
}
fn main() {
let user_input = String::from("start");
handle_command(&user_input);
}
Это выносит логику в отдельную функцию с параметром &str, что улучшает читаемость и переиспользование кода.