Возвращает локальную строку как срез (& str)

Есть несколько вопросов, которые, похоже, касаются одной и той же проблемы, с которой я сталкиваюсь. Например, см. здесь и здесь. В основном я пытаюсь создать String в локальной функции, но затем верну его как &str. Нарезка не работает, потому что срок службы слишком короткий. Я не могу использовать str непосредственно в функции, потому что мне нужно ее динамически строить. Тем не менее, я также предпочел бы не возвращать String, так как природа объекта, в который он входит, является статическим после его создания. Есть ли способ получить торт и съесть его?

Здесь минимальное некомпилируемое воспроизведение:

fn return_str<'a>() -> &'a str {
    let mut string = "".to_string();

    for i in 0..10 {
        string.push_str("ACTG");
    }

    &string[..]
}

Ответ 1

Нет, вы не можете этого сделать. Есть как минимум два объяснения, почему это так.

Во-первых, помните, что ссылки заимствованы, т.е. указывают на некоторые данные, но не владеют им, они принадлежат кому-то другому. В этом конкретном случае строка, срез, к которой вы хотите вернуться, принадлежит функции, потому что она хранится в локальной переменной.

Когда функция выходит, все ее локальные переменные уничтожаются; это включает вызов деструкторов, а деструктор String освобождает память, используемую строкой. Однако вы хотите вернуть заимствованную ссылку, указывающую на данные, выделенные для этой строки. Это означает, что возвращаемая ссылка немедленно становится свисающей - она ​​указывает на недопустимую память!

Rust был создан, среди всего прочего, для предотвращения таких проблем. Поэтому в Rust невозможно вернуть ссылку, указывающую на локальные переменные функции, что возможно на таких языках, как C.

Есть еще одно объяснение, немного более формальное. Посмотрите на свою подпись функции:

fn return_str<'a>() -> &'a str

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

let s: &'static str = return_str();

Для этого требуется 'a быть 'static, но это, конечно, невозможно - ваша функция не возвращает ссылку на статическую память, она возвращает ссылку со значительно меньшим временем жизни. Таким образом, такое определение функции является необоснованным и запрещено компилятором.

В любом случае, в таких ситуациях вам нужно вернуть значение принадлежащего ему типа, в этом конкретном случае оно будет принадлежать String:

fn return_str() -> String {
    let mut string = String::new();

    for _ in 0..10 {
        string.push_str("ACTG");
    }

    string
}

Ответ 2

В некоторых случаях вы передаете строковый фрагмент и можете условно хотеть создать новую строку. В этих случаях вы можете вернуть Cow. Это позволяет ссылаться, когда это возможно, и принадлежащую String иначе:

use std::borrow::Cow;

fn return_str<'a>(name: &'a str) -> Cow<'a, str> {
    if name.is_empty() {
        let name = "ACTG".repeat(10);
        name.into()
    } else {
        name.into()
    }
}

Ответ 3

Вы можете выбрать утечку памяти для преобразования String в &'static str:

fn return_str() -> &'static str {
    let string = "ACTG".repeat(10);

    Box::leak(string.into_boxed_str())
}

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

Если вы хотите вернуть одну и ту же строку для каждого вызова, см. Также: