Из гиттера: "Почему env_path будет очищен раньше чем я сформирую структуру?"

Спасаю из бездны ruRust/easy гиттера, вдруг кому пригодится в будущем:

Nikolay Govorov @nikolay-govorov:
Где я не прав? Почему env_path будет очищен раньше чем я сформирую структуру?

pub struct Shell<'a> {
    os_path: Vec<&'a str>,
}

impl<'a> Shell<'a> {
    pub fn new() -> Shell<'a> {
        let env_path = env::var_os("PATH").unwrap().into_string().unwrap();
        let os_path: Vec<&'a str> = env_path.as_str().split(':').collect();
        Shell { os_path }
    }
}

Ilya Bogdanov @vitvakatu:
Потому что ты берешь as_str, который возвращает &str, а затем разбиваешь этот &str на &'a str. Ну сам то подумай, у тебя из функции выходит Shell<'a>, а на входе никакого лайфтайма нет - значит ты возвращаешь структуру, лайфтаймом связанную с чем-то внутри тела функции (это можно выявить глядя только на сигнатуру)


Nikolay Govorov @nikolay-govorov:
@vitvakatu, а какое решение? Я должен вернуть что-то связанное с внешним лайфтаймом, но как?

pub struct Shell<'a> {
    os_path: Vec<&'a str>,
}

impl<'a> Shell<'a> {
    pub fn new() -> Shell<'a> {
        let env_path = env::var_os("PATH").unwrap().into_string().unwrap();
        let os_path: Vec<&str> = env_path.split(':').collect();
        Shell { os_path }
    }
}

Ilya Bogdanov @vitvakatu:
@nikolay-govorov Rust Playground


Alexander Irbis @irbis:
@nikolay-govorov в таких ситуациях есть два подхода.

Самый простой и дорогой: можно создавать владеющие ссылки (Box, Rc, Arc, Gc) - это иногда неизбежное, но достаточно дорогое решение, особенно, если нужно больше одного выделения памяти. Однако это вполне приемлемо, если это одноразовая операция и результат в дальнейшем многократно переиспользуется. Например, на старте программы подготавливаешь конфигурацию, а потом программа минутами/часами/днями работает.

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

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

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

Что я имею в виду под “выделение памяти - дорого”: обычно выделение памяти требует захвата мьютекса (возможно даже не одного, зависит от того, как устроен аллокатор) - это выливается в несколько микросекунд на современных процессорах. Проход по нескольким сотням байтов с поиском вполне может укладываться в несколько наносекунд. То есть, разница по времени примерно в тысячу раз.

Однако, если время работы программы не жмёт, нет высокой нагрузки, требующей выжимать всё из железа, - вероятно нет смысла изощряться и делать что-то сложнее самого наивного первого варианта. Впрочем, сделать итератор - тоже несложно.

А почему бы не положить env_path тоже в структуру…
os_path будет ссылаться на env_path, и не нужен будет life time (наверное)

А еще лучше будет env_path делать не into_string а to_str. А os_path уже в строку

Не, самоссылающиеся структуры в расте это вообще дикая боль. См:

Предложение Ильи вполне правильное.

1 лайк

А есть где-то в интернете FAQ по подобным кейсам борьбы с borrow-checker?

Так как очевидно, все начинающие раст сталкиваются рано или поздно именно с этим, и относительно безболезненными решениями будет:

  1. вытащить то, что должно жить - вверх
  2. структура возврата должна возвращать данные, а не ссылки.

Полноценной подборки не знаю, но тему вроде такой гуглятся:

2 лайка

Может быть не совсем отвечающая на последний вопрос, но достаточно обстоятельная статья о несовершенстве borrow checker’а, заставляющем с ним бороться, когда, казалось бы, уже не должно быть борьбы и будущих (настоящих) доработках и улучшениях, спасающих от ненужной борьбы: https://medium.com/@GolDDranks/things-rust-doesnt-let-you-do-draft-f596a3c740a5 Полагаю, многим будет интересно почитать, поскольку хорошо расписывается “как всё устроено” и почему всё (пока) не так как хочется.

2 лайка