Почему переменная data заимствуется после вызова функции?

fn test<'a>(x:&'a mut &'a u8){ }

fn main() {
    let mut data = &1;
    test(&mut data);
    println!("{:p}", data);
}

Run

Известно, что эта проблема решается разделением времен жизни в функции:

fn test<'a,'b>(x:&'a mut &'b u8){ }

Вопрос в том, почему вообще это происходит?

Одинаковые времена жизни должны приводить только к тому, что ссылка data будет не 'static, а ограничится локальной областью функции main().

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


Ответ: 42

1 лайк

:snail: Первая ссылка должна дожить до println + вторая (изменяемая ссылка) должна иметь точно такое же время жизни -> println не может использовать первую ссылку, потому что жива вторая (изменяемая).

Если убрать println, то все собирается, потому что не возникает конфликта уникальности.

Время жизни самой ссылки не обязательно должно быть равно времени жизни данных на которые она указывает. Оно вполне может быть меньше, и для мутабельных ссылок тоже. Вторая ссылка не обязана быть живой весь интервал, вж первой - это верхний предел второй.

Даже если переписать вот так, то ничего не меняется:

    let mut data = &1;
    {   
        let t = &mut data;
        test(t);
    }
    println!("{:?}", data);

Тут вторая ссылка t никак не может быть живой к моменту вызова println!(). А в сообщении об ошибке Rust пишет, что mutable borrow длится до конца main().

Это все да, но ты сам в сигнатуре явно требуешь что бы время жизни было одинаковым - оно и становится одинаковым, т.е. весь main. БЧ же довольно прямолинейная штуковина.

По-моему ты путаешь вж самой ссылки и вж данных на котрые она указывает. Тут одно правило для всех - вж ссылки не может быть больше чем вж данных на которые она указывает. Вот это 'a - это как раз время жизни значения 1u8.

Тут время жизни первой ссылки (самой переменной data) должно быть равно времени жизни значения на которое она указывает из-за того, что вторая ссылка - мутабельная (инвариантность и все дела). А время жизни второй ссылки (переменной t) определяется только скопом в котором она объявлена.

Мне кажется что из-за явного требования одинаковости вж это просто некорректный код, который в принципе не должен работать. Но возможно я и правда туплю, хз. @IBUzPE9, ты не против если я продублирую вопрос с { let t = &mut data; test(t);} на англоязычный SO? Вдруг кто из команды ядра посмотрит и прям по полочкам все разложит.

Да пожалуйста. Может быть даже лучше на https://users.rust-lang.org/

Что ж, на мою попытку Shepmaster отправил искать отличия от этого вопроса:

Который разжевывает то же, что я выше пытался сказать - жесткое требование одного времени жизни невозможно удовлетворить.
Правда, меня все равно немного смущает что в сообщении об ошибке говорится не об этом, а об уникальности. Но, видимо, код просто сломан слишком сильно.

Привет! Не эксперт по lifetimeам, но, мне кажется, тут играет роль две вещи:

  • &mut T инвариантен по T, соответственно, &'a mut &'a i32 означает, что lifetime должен быть ровно 'a , не больше и не меньше

  • & ссылки, в отличие от &mut, не делают reborrowing автоматически. Т.е, такой код работает: let t = &mut (&*data);

Соответственно, мы не можем ни через variance, ни через reborrowing сократить lifetime.

EDIT: кто найдёт норм доку про reborrowing, тому конфетку. Я лучше чем второй пункт отсюда не знаю :frowning: https://bluss.github.io/rust/fun/2015/10/11/stuff-the-identity-function-does/

1 лайк

Программа работает во время выполнения функции main. Время жизни static действует в это же время (условно, есть же еще и константы и статические переменные). У data вж будет static, оно не укоротится из-за того, что используется в test. Ответ на похожий вопрос: Why does RefCell influence lifetimes? - #2 by matklad - help - The Rust Programming Language Forum, — иначе никак не объяснить, мне кажется

@ozkriff Ну я бы не сказал что он там прямо все разжевал. По сути просто сказал что так нельзя и надо ввести разные времена жизни. Вобще там рассматривается более запутанный случай и понять, что происходит что-то не то гораздо сложнее.

@Virtuos86 Может быть я не совсем понятно сформулировал. Суть в том что если явно задать вж 'static:

   let mut data:&'static u8 = &1;
   {
       let t = &mut data;
       test(t);
   }
   println!("{:?}", data);

Rust начинает ругаться что data недостаточно долго живет и что она должна быть 'static. А если вызов test() закомментировать все будет работать. Это как раз проявляется эффект инвариантности мутабельных ссылок о котором выше сказал @matklad.

Но все эти интересные эффекты заимствование должно закончиться когда t покидает область видимости. А оно почему-то не заканчиваются.

@matklad Нету такой доки. Раньше это совсем вскользь упоминалось в номиконе, сейчас остался только русский перевод. Там сложно что-то понять.
Я про это когда-то загадку придумал, наверно все уже видели.

Я переписал пример, как учит Nomicon:

fn test<'a>(x:&'a mut &'a u8){ }

fn main() {
    'b: {
        let mut data = &'b 1;
        'c: {
            test(&'c mut data);
            'd: {
                println!("{:p}", data);
            }
        }
    }
}

Но единственное, что понял, что test должен быть таким:

fn test<'a, 'b: 'a>(x:&'a mut &'b u8) {}

после чего код компилируется. Кстати, как работает параметр “p” в строке формата:

println!("{:p}", data);

берется указатель на data, и это считается reborowing?

Так а почему должны? Вроде бы не должны, ровно потому что нет reborrowing. Сигнатура test говорит, что вж в t и в data ровно совпадают, и вж в data должно как минимум покрывать println, значит и вж в t должно его покрывать. Ну и дальше, в зависимости от скоупа, получаем либо вж, которое оказывается дольше дольше скоупа переменной, либо уникальную и шаренную ссылку одновременно.

Я думаю, вся фигня в том, что текущая система лайфтаймов лексическая. Конечно, t уничтожится, но системе на это пофиг. Если в сигнатуре test указано, что обе ссылки имеют одинаковое время жизни, но они и не используются в функции (а значит, и не удаляются по выходе из нее, т.е. “внутренний”, более узкий лайфтайм не используется), значит берется другой лайфтайм, и обе ссылки считаются валидными до конца main.

@Virtuos86 Да {:p} просто от экспериментов осталось, можно заменить на {:?}. Там data просто заимствуется немутабельно. Reborrowing вот так &*data.

Я думаю, вся фигня в том, что текущая система лайфтаймов лексическая

По моему всей фигни это не объясняет. :slight_smile: В доке как минимум такие частные случаи не описаны почему-то.

@matklad Ну так вж того на что указывет t не означает что само t должно покрывать это вж.

{
    let x:&'static = &1;
}
println!("{}", x);

То что x указывает на статик не делает его доступным вне собственного скоупа?

Ну с явным reborrowing работает т.к. там видимо временная переменная создается которую уже заимствует t. Такое работает:

    let mut data:&'static u8 = &1;
    {
        let mut s = &*data;
        let t = &mut s;
        test(t);
    }
    println!("{:?}", data);

Тут в s вж изменился и оно уже не статик

Что-то я ошибся: используются, к “x” ведь биндится ссылка. Ну, не знаю…

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

Запостил альтернативный ответ с отсылкой к твоему вопросу. По-моему, не стоило соглашаться что это дубликат.

Вот облом, такое решение небезопасно. Придется удалить, вот что там было.

То есть получается что так делать тоже нельзя?

    let mut data:&'static u8 = &1;
    {
        let mut s = &*data;
        let t = &mut s;
        test(t);
    }
    println!("{:?}", data);

Кажется я нашел почему заимствование продолжается. Если бы оно заканчивалось то вот это бы приводило к использованию после освобождения:

fn test<'a>(_x:&'a mut &'a str){}

fn main() {
    let mut data:&'static str = "hello";
    {
        let x = "ups".to_owned();
        let mut s = &*data;
        let t = &mut s;
        test(t);
        *t = &x;
    }
    println!("{:?}", data);
}

Да нет тут все было бы норм. После перезаимствования через t можно менять s, а data уже никак с t не связана и ее изменить нельзя.
А в своем ответе я этим “волшебным” трансмьютом как раз переписал времена жизни как-будто t ссылается на локальную переменную s, в то время как она указывала на внешнюю data.
Заслуженный фейл. :frowning_face:

Видел что Леша завел в IRLO тему

2 лайка

Хм, оказывается на небезопасные мутабельные статики правила заимствования не действуют:

    unsafe{
        static mut data:u8 = 1;
        let tmp_mut = &mut data;
        data = 2;
        println!("{:?}", tmp_mut);
    }