Странное поведение вложенных мутабельных ссылок


#1

Проблема проявляется при преобразовании вложенной ссылки в нормальную с исходным временем жизни. В случае немутабельных ссылок все прекрасно работает:

fn deref_rr<'a,'b>(x: &'a &'b u8) -> &'b u8 {x}

И с внешними мутабельными ссылками тоже все работает:

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

А вот с внутренними мутабельными ссылками возникают проблемы:

fn deref_rm<'a,'b>(x: &'a &'b mut u8) -> &'b u8 {x} // lifetime mismatch
fn deref_mm<'a,'b>(x: &'a mut &'b mut u8) -> &'b u8 {x} // lifetime mismatch
fn deref_mut_mm<'a,'b>(x: &'a mut &'b mut u8) -> &'b mut u8 {x} // lifetime mismatch

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

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

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

ЗЫ: Вышеизложенный поток сознания - результат медитации над вопросом на stackoverflow


Ответ где-то рядом


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

Мда, я путаюсь в этом “интуитивном” синтаксисе.

Я так понимаю по умолчанию Rust должен сам выводить именно 'b: 'a.

Для параметров-ссылок функции компилятор не выводит лайфтаймы самостоятельно, только ручками.


#3

'a:'b означает что 'a переживет 'b - внешняя переживет внутреннюю. С учетом свойств ссылок это вырожденный случай когда 'a == 'b.
С таким условием даже немутабельный deref_rr() нельзя использовать:

fn deref_rr<'a:'b, 'b>(x: &'a &'b u8) -> &'b u8 {
    x
}

fn main() {
    let mut x1 = 1u8;
    let r1:&u8 = {
        let t = &x1;
        deref_rr(&t)
    };
    println!("{}", r1);
}
error[E0597]: `t` does not live long enough

А если заменить 'a:'b на 'b:'a то этот пример начинает работать. Я так понимаю по умолчанию Rust должен сам выводить именно 'b:'a.

Проблема именно в том чтобы получить внутреннюю ссылку с внутренним временем жизни, которое больше чем вж внешней.


#4

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


#5

Может быть, но в данном случае явное указание 'b:'a ничего не меняет.


#6

Ну как, текст ошибки немного меняется. Про лайфтаймы параметров в Книге написано:

However, when a function has references to or from code outside that function, it becomes almost impossible for Rust to figure out the lifetimes of the parameters or return values on its own. The lifetimes might be different each time the function is called. This is why we need to annotate the lifetimes manually.


#7

Хм, да действительно меняется. Но понятнее не становится.

  --------------     ------  ^ ...but data from `x` is returned here
  |
  this parameter and the return type are declared with different lifetimes...
 --------------             ^ ...but data from `x` flows into `x` here
 |
 these two types are declared with different lifetimes...

Номер ошибки остается тот же самый.
Я бы даже сказал он становится менее понятным. Rust почему то двойную ссылку &'a &'b mut u8 начинает называть “these two types”. Впрочем это мелочи, такое с ним бывает :thinking:


#8

Вот ответ который все объясняет.

Если бы эта функция работала:

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

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

Тут есть интересный момент - если задать время жизни внешней ссылки большим или равным времени жизни внутренней то функция будет компилироваться без ошибок:

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

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

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

fn main() {
    let mut data = 1u8;
    let mdata:&mut u8 = &mut data;
    let rdata:&u8 = deref_rm(&mdata); //let rdata:&u8 = &*mdata;
    println!("{}", rdata);
    println!("{}", mdata);
    //*mdata = 2u8; //cannot assign to `*mdata` because it is borrowed
}