Avatar ·

Правильный способ вернуть новую строку из функции в Rust

Привет! Я новичок в Rust и пытаюсь разобраться с возвратом строк из функций. В других языках я просто создавал строку и возвращал её, но в Rust всё сложнее из-за системы владения. У меня есть функция, которая должна принимать строку, добавлять к ней префикс и возвращать новую строку. Вот мой код, который не работает: ```rust fn add_prefix(s: &str) -> &str { let result = String::from("prefix_") + s; &result } ``` Компилятор выдаёт ошибку: `cannot return reference to local variable 'result'`. Я понимаю, что `result` удаляется после выхода из функции, но как тогда правильно вернуть новую строку? Я пробовал разные варианты: 1. Возвращать `String` вместо `&str` — но тогда в вызывающем коде нужно заново заимствовать строку. 2. Использовать `to_string()` — но это кажется избыточным. 3. Пытался вернуть `&'static str` — но это не подходит для динамических строк. Подскажите, какой подход считается идиоматичным в Rust для возврата новых строк из функций? Хочется понять не только «как сделать», но и «почему так правильно». Заранее спасибо! <<>>

Основная проблема: владение и время жизни

Ошибка, которую вы получили, — классическая для новичков в Rust. Она связана с тем, что result — это локальная переменная, которая живёт только внутри функции. Когда функция завершается, память, выделенная под result, освобождается. Возвращать ссылку на уже удалённые данные нельзя — это приводит к висячим указателям, которых Rust не допускает.

Правильное решение: возвращайте String, а не &str

Самый идиоматичный способ — вернуть String. В Rust строка String владеет своими данными, поэтому её можно безопасно передать из функции наружу. Вызывающий код получит право собственности на эту строку.

fn add_prefix(s: &str) -> String {
    let result = String::from("prefix_") + s;
    result  // или просто return result;
}

fn main() {
    let original = "hello";
    let new_string = add_prefix(original);
    println!("{}", new_string); // "prefix_hello"
    // new_string теперь владеет данными, мы можем использовать её дальше
}

Упрощённый вариант без промежуточной переменной

Часто в Rust пишут короче, используя макрос format! или цепочку методов:

fn add_prefix(s: &str) -> String {
    format!("prefix_{}", s)
}

// Или через to_string() и конкатенацию:
fn add_prefix_v2(s: &str) -> String {
    "prefix_".to_string() + s
}

Почему это правильно?

  • Владение: Функция создаёт новую строку, владеет ею и передаёт владение вызывающему коду. Никаких ссылок на локальные данные.
  • Производительность: String хранит данные в куче, но возврат через перемещение — это дешёвая операция (копируется только указатель, длина и ёмкость).
  • Гибкость: Вызывающий код может решить, что делать со строкой: взять срез (&str), модифицировать, передать дальше и т.д.

Когда всё же возвращать &str?

Ссылку &str можно возвращать только в двух случаях:

  1. Если строка является статической (&'static str) — например, строковый литерал.
  2. Если строка является частью данных, которыми владеет кто-то другой (например, поле структуры, полученное через аргумент).

Пример с &'static str:

fn get_greeting() -> &'static str {
    "Hello, world!"  // живёт всю программу
}

Дополнительный совет: используйте String как тип возврата

В Rust принято возвращать String из функций, которые создают новые строки. Не бойтесь, что это приводит к лишним аллокациям — компилятор часто оптимизирует такие случаи. Если вы заметите проблемы с производительностью, можно будет рассмотреть альтернативы (например, передачу &mut String в аргументе), но для 99% случаев возврат String — это правильный и чистый подход.

<<>>

Альтернативный подход: передача изменяемой ссылки на буфер

Если вы пишете высокопроизводительный код и хотите избежать лишних аллокаций, можно передавать в функцию изменяемую ссылку на String и дописывать данные в неё. Это полезно, когда вы многократно вызываете функцию в цикле.

fn add_prefix_into(s: &str, buffer: &mut String) {
    buffer.push_str("prefix_");
    buffer.push_str(s);
}

fn main() {
    let mut result = String::new();
    add_prefix_into("hello", &mut result);
    println!("{}", result); // "prefix_hello"
}

Когда это оправдано?

  • В горячих циклах, где каждая аллокация на счету.
  • Когда нужно переиспользовать один и тот же буфер (например, в парсерах или сериализаторах).

Недостатки

  • Менее идиоматично: Rust поощряет возврат значений, а не мутацию аргументов.
  • Усложняет интерфейс: вызывающий код должен создать и поддерживать буфер.

Для большинства задач возврат String — это правильный выбор. Используйте мутацию буфера только после профилирования, когда вы точно знаете, что это узкое место.