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

Это все да, но ты сам в сигнатуре явно требуешь что бы время жизни было одинаковым - оно и становится одинаковым, т.е. весь 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);
    }

Mutable statics

If a static item is declared with the mut keyword, then it is allowed to be modified by the program. One of Rust’s goals is to make concurrency bugs hard to run into, and this is obviously a very large source of race conditions or other bugs. For this reason, an unsafe block is required when either reading or writing a mutable static variable. Care should be taken to ensure that modifications to a mutable static are safe with respect to other threads running in the same process.

Mutable statics are still very useful, however. They can be used with C libraries and can also be bound from C libraries in an extern block.

Тут получается и с одним потоком можно легко “застрелиться”. Думаю что в одном потоке все таки можно было бы отслеживать ссылки во время компиляции. Сделали видимо как проще, все равно unsafe.

На stackowerflow мне объяснили как на самом деле работают времена жизни. Мне не слишком нравится эта концепция, но она объясняет что происходит в примере в шапке.
Суть вот в чем:

Длительность заимствования определяется только временем жизни в ссылке, которое вычисляется компилятором.

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

Похоже, что единственная ситуация, когда явно проявляется то что заимствование может продолжаться после выхода ссылки из области видимости - это ссылка вида &'a mut T<'a>. Во всяком случае я не смог найти других примеров. Если кто-нибудь сможет - кидайте сюда.

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

Честно говоря не вижу смысла в такой реализации времен жизни. Назначение механизма заимствования - предотвращение гонок при изменении данных. Зачем может быть нужно продолжать блокировать доступ к данным после того, как мутабельная ссылка на эти данные, покинула область видимости? Всегда считал что время жизни в ссылке - это верхняя граница, до котрой эта ссылка может дожить, а длительность заимствования данных определяется тем, где ссылка покидает область видимости (или в случае NLL тем где ссылка последний раз использовалась).

Документация в этом вопросе не особенно вдается в подробности. Вот определение времени жизни из Растономикона:

Each reference, and anything that contains a reference, is tagged with a lifetime specifying the scope it’s valid for.

То есть время жизни определяет область в которой ссылка действительна. Не знаю можно ли “область в которой ссылка действительна” трактовать как “область в которой заимствование действительно”?

Хотя дальше есть пара примеров, в комментариях которых говорится что длительность заимствования равна выбранному времени жизни:

Пример 1:

fn as_str<'a>(data: &'a u32) -> &'a str {
    'b: {
        let s = format!("{}", data);
        return &'a s
    }
}

fn main() {
    'c: {
        let x: u32 = 0;
        'd: {
            // An anonymous scope is introduced because the borrow does not
            // need to last for the whole scope x is valid for. The return
            // of as_str must find a str somewhere before this function
            // call. Obviously not happening.
            println!("{}", as_str::<'d>(&'d x));
        }
    }
}

Пример 2:

'a: {
    let mut data: Vec<i32> = vec![1, 2, 3];
    'b: {
        // 'b is as big as we need this borrow to be
        // (just need to get to `println!`)
        let x: &'b i32 = Index::index::<'b>(&'b data, 0);
        'c: {
            // Temporary scope because we don't need the
            // &mut to last any longer.
            Vec::push(&'c mut data, 4);
        }
        println!("{}", x);
    }
}
1 лайк