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


#1
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


Конфликт времен жизни
#3

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

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


#4

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

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

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

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


#5

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


#6

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

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


#7

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


#8

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


#9

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

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


#10

Привет! Не эксперт по 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/


#11

Программа работает во время выполнения функции main. Время жизни static действует в это же время (условно, есть же еще и константы и статические переменные). У data вж будет static, оно не укоротится из-за того, что используется в test. Ответ на похожий вопрос: https://users.rust-lang.org/t/why-does-refcell-influence-lifetimes/19648/2, — иначе никак не объяснить, мне кажется


#12

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

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

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

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

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

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


#13

Я переписал пример, как учит 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?


#14

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


#15

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


#16

@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 вж изменился и оно уже не статик


#17

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


#18

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


#19

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

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

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

    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:


#20

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


#21

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

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