Как правильно соединять строки в Rust: concat, format! или что-то ещё?
Основные способы соединения строк в Rust
Ваша проблема типична для новичков. В Rust строки устроены иначе, чем в Python или JavaScript. Есть два основных типа: &str (строковый срез, неизменяемый) и String (изменяемая, владеющая строка). Оператор + работает только с String слева, а справа принимает &str. Вот лучшие практики:
1. Макрос format! — универсальный выбор (рекомендуется)
Это самый гибкий способ. Он работает как println!, но возвращает String. Не верьте слухам о медлительности — современный Rust-компилятор оптимизирует его отлично.
let hello = "Hello";
let world = "World";
let message = format!("{} {}!", hello, world); // "Hello World!"
println!("{}", message);
// С числами и выражениями:
let age = 30;
let greeting = format!("Привет, мне {} года", age); // "Привет, мне 30 года"
2. Метод push_str — для пошагового накопления
Если вы собираете строку по частям в цикле или в функции, используйте String и метод push_str:
let mut result = String::from("Имена: ");
let names = ["Анна", "Борис", "Виктор"];
for name in &names {
result.push_str(name);
result.push_str(", ");
}
// result = "Имена: Анна, Борис, Виктор, "
3. Оператор + (только для String)
Можно использовать, если у вас уже есть String слева. Помните: он «забирает владение» левым операндом (переносит его).
let mut s = String::from("Hello ");
s = s + "World"; // s теперь "Hello World"
// Но s = s + "!" + "?"; // Так нельзя, нужно: s = s + "!" + "?"; // Ошибка!
// Правильно: s = s + "!"; s = s + "?";
4. Метод concat и join для коллекций
Для соединения массива строк используйте concat() (без разделителя) или join() (с разделителем):
let parts = ["one", "two", "three"];
let joined = parts.join(", "); // "one, two, three"
let concatenated = parts.concat(); // "onetwothree"
// Для Vec<String> тоже работает:
let vec = vec!["a".to_string(), "b".to_string()];
let result = vec.join(""); // "ab"
Когда что использовать?
- Для простого соединения 2-5 строк —
format!(читаемо и безопасно). - Для цикла или накопления —
push_str(эффективно по памяти). - Для коллекций строк —
join()илиconcat(). - Избегайте цепочек из
+— это приводит к множественным аллокациям и ухудшает читаемость.
Пример реального кода:
fn build_greeting(name: &str, title: &str) -> String {
format!("Уважаемый {} {}, добро пожаловать!", title, name)
}
fn main() {
let mut log = String::new();
for i in 0..10 {
log.push_str(&format!("Шаг {}: OK\n", i));
}
println!("{}", log);
}
Запомните: format! — ваш друг. Он читается как обычный текст и не требует думать о владении. Производительность в 99% случаев не имеет значения, а если понадобится — всегда можно оптимизировать потом.
Альтернативный подход: крейты для продвинутой работы со строками
Если вы работаете с большим количеством строк или нужна особая производительность, обратите внимание на внешние крейты:
1. Крейт itertools — для сложных комбинаций
use itertools::Itertools;
let words = ["Hello", "from", "Rust"];
let sentence: String = words.iter().join(" "); // "Hello from Rust"
2. Крейт str_concat — для супербыстрой конкатенации
Если у вас тысячи строк и важна скорость:
use str_concat::concat;
let parts = vec!["a", "b", "c"];
let result = concat(&parts); // быстрее, чем format! для больших объёмов
3. Использование String::with_capacity для оптимизации
Если вы знаете итоговую длину, зарезервируйте память заранее:
let mut s = String::with_capacity(100);
s.push_str("Hello");
s.push_str(" World"); // без лишних перераспределений
Для большинства задач format! и push_str вполне достаточно. Продвинутые методы используйте только если профилировщик показал, что конкатенация — узкое место.