Введение: Почему в Rust нельзя просто сложить строки?
Если вы перешли на Rust из Python или JavaScript, то первое, что вас, скорее всего, шокировало — это попытка написать "Hello, " + "world!". Компилятор встречает вас красной волной и загадочным сообщением об ошибке. В мире Python такое сработало бы мгновенно, но Rust — это другой зверь. Вопрос "How do I concatenate strings?" на Stack Overflow набрал сотни тысяч просмотров, и это не случайно.
Проблема в том, что конкатенация строк в Rust — это не просто синтаксический сахар. Это вопрос управления памятью, владения (ownership) и типов данных. Если вы новичок, вы легко можете наступить на классические грабли: потерять владение строкой, случайно скопировать гигабайты данных или просто запутаться в String и &str.
В этой статье мы разберём 5 самых частых ошибок, которые совершают новички при склеивании строк в Rust, и покажем, как их избежать. Вы узнаете, когда использовать +, когда format!, а когда push_str. Поехали!
Ошибка №1: Забыть про взятие владения (ownership)
Как это выглядит
Представьте, что вы пишете код:
let s1 = String::from("Привет");
let s2 = String::from(", мир!");
let s3 = s1 + &s2; // Ошибка? Или нет?
println!("{}", s1); // Ошибка компиляции!
Вы ожидаете, что s1 останется доступным, но компилятор говорит: "Нет, s1 был перемещён (moved)".
В чём суть ошибки
В Rust оператор + для строк использует сигнатуру fn add(self, s: &str) -> String. Обратите внимание на self — это значит, что первая строка потребляется (consumes) и вы больше не можете её использовать. Вы буквально "отдаёте" s1 функции add, и она становится недоступной.
Как исправить
Есть два пути:
- Клонировать строку (если вам нужна исходная):
let s3 = s1.clone() + &s2; - Изменить порядок или использовать
format!:
let s1 = String::from("Привет");
let s2 = String::from(", мир!");
let s3 = format!("{}{}", s1, s2); // s1 и s2 остаются доступными
println!("{}", s1); // Работает!
Совет: Если вы не планируете использовать s1 после конкатенации, смело используйте +. Это быстрее, чем format!, так как не создаёт новый буфер с нуля.
Ошибка №2: Путаница между String и &str
Как это выглядит
Вы пытаетесь сложить String с литералом:
let s = String::from("Hello");
let result = s + " world"; // Работает!
Но если вы попробуете наоборот:
let s = "Hello";
let result = s + String::from(" world"); // Ошибка!
В чём суть ошибки
Оператор + работает только когда левая часть — это String, а правая — &str. Литерал " world" автоматически является &str, поэтому первый пример работает. Во втором примере s — это &str, у которого нет метода add.
Как исправить
Преобразуйте &str в String или используйте format!:
let s = "Hello";
let result = format!("{} {}", s, "world"); // Универсальный способ
// Или
let result = s.to_string() + " world"; // to_string() создаёт String
Золотое правило: String — это владеющий тип (owned), &str — это заимствование (borrow). Конкатенация всегда требует владения слева.
Ошибка №3: Игнорирование производительности
Как это выглядит
Вы склеиваете 1000 строк в цикле:
let mut result = String::new();
for i in 0..1000 {
result = result + &i.to_string(); // Медленно!
}
В чём суть ошибки
Каждый раз при использовании + или format! создаётся новая строка, а старая удаляется. Это приводит к множественным аллокациям памяти и копированию данных. Для больших объёмов это катастрофа по скорости.
Как исправить
Используйте push_str или push для добавления в существующую строку:
let mut result = String::with_capacity(1000 * 10); // Заранее выделяем память
for i in 0..1000 {
result.push_str(&i.to_string());
}
Бонус: Метод with_capacity позволяет заранее зарезервировать память, чтобы избежать лишних реаллокаций. Это особенно полезно, если вы знаете примерный размер итоговой строки.
Сравнение производительности (грубо):
+в цикле: O(n²) по времени и памятиpush_strсwith_capacity: O(n) — линейно
Ошибка №4: Использование format! там, где не нужно
Как это выглядит
let name = "Алексей";
let greeting = format!("{}", "Привет, " + name); // Избыточно
В чём суть ошибки
Макрос format! — это мощный инструмент, но он создаёт новую строку каждый раз. Если вам нужно просто склеить две строки, format! может быть медленнее, чем прямой вызов push_str или +. Кроме того, это делает код менее читаемым.
Как исправить
Используйте format! только когда вам нужно сложное форматирование (числа, выравнивание, несколько переменных). Для простой конкатенации двух строк:
let greeting = format!("Привет, {}!", name); // Хорошо
// Но если просто две строки:
let s1 = String::from("Hello");
let s2 = String::from("World");
let result = s1 + "