Konflikt mutable/immutable reference po wprowadzeniu parametru czasu życia

0

Cześć, uczę się Rusta i próbuję poprawnie użyć parametru czasu życia. Poniższa funkcja i wywołanie działały prawidłowo, zanim nie dodałem parametrów czasu życia:

fn reading<'a>(input : &mut dyn Read, tr : &'a mut TermReader<'a>) {
    let mut buff : [u8;10] = [0;10];

    while match input.read(&mut buff) {
        Ok(len) => { tr.accept(&buff[0..len]) },
        Err(e) => {
            println!("ERROR: {}", e);
            false
        }
    } {}
}

wywołanie:

 let mut tr = TermReader::new(initial_keys, Some(KeyAction::Action(ac_elsekey)));
 reading(&mut input, &mut tr);
 println!("{}", tr.args.join(","));

Po dodaniu parametru czasu życia otrzymuję następujący komunikat:

error[E0499]: cannot borrow `*tr` as mutable more than once at a time
  --> src/main.rs:125:22
   |
121 | fn reading<'a>(input : &mut dyn Read, tr : &'a mut TermReader<'a>) {
   |            -- lifetime `'a` defined here
...
125 |         Ok(len) => { tr.accept(&buff[0..len]) },
   |                      ^^^^^^^^^^^^^^^^^^^^^^^^
   |                      |
   |                      `*tr` was mutably borrowed here in the previous iteration of the loop
   |                      argument requires that `*tr` is borrowed for `'a`

error[E0502]: cannot borrow `tr.args` as immutable because it is also borrowed as mutable
  --> src/main.rs:201:20
   |
200 |     reading(&mut input, &mut tr);
   |                         ------- mutable borrow occurs here
201 |     println!("{}", tr.args.join(","));
   |                    ^^^^^^^^^^^^^^^^^
   |                    |
   |                    immutable borrow occurs here
   |                    mutable borrow later used here

Co robię nie tak? Dlatego bez lifetime'a było ok, a teraz nie jest? Jak to naprawić?

4

Gdyby ten kod się skompilował, mógłbyś łatwo doprowadzić do use-after-free:

struct TermReader<'a> {
    data: &'a [u8],
}

impl<'a> TermReader<'a> {
    fn accept(&mut self, data: &'a [u8]) {
        self.data = data;
    }
}

fn reading<'a>(input : &mut dyn Read, tr : &'a mut TermReader<'a>) {
    /* ... */
}

fn main() {
    let mut tr = TermReader {
        data: &[]
    };
    
    reading(&mut Cursor::new(vec![1, 2, 3]), &mut tr);

    // dokąd teraz wskazuje tr.data?
}

Problem wynika z tego, że buff żyje jedynie przez chwilę, wewnątrz reading(), a tworząc lifetime &'a mut TermReader<'a> (z funkcją .accept(), jak domyślam, odnoszącą się też do 'a) mówisz, że buff musi żyć tyle samo (albo dłużej) co tr -- a to jest niemożliwe, bo tr jest tworzony na zewnątrz reading(), przez co siłą rzeczy musi żyć dłużej niż buff (tworzone wewnątrz reading()).

W obecnej postaci ten problem można rozwiązać jedynie poprzez zmianę funkcji accept() tak, aby nie wymagała data: &'a [u8], a raczej data: &[u8] - to upewni się, że to &[u8] (które może żyć krócej niż TermReader) przypadkiem nie zostanie wrzucone do środka TermReadera.

Dlatego bez lifetime'a było ok, a teraz nie jest? Jak to naprawić?

Możesz podrzucić tę wersję, która działała? Strzelam, że Twoje .accept() teraz ma &'a [u8], którego wcześniej nie miało.

0

Teraz funkcja accept wygląda tak:

impl<'a> TermReader<'a> {
    //...
    
    fn accept(&'a mut self, keys : &[u8]) -> bool {
        if self.key_map.contains_key(&keys[0]) {
            let x = self.key_map[&keys[0]];
            x.run(self, keys)
        } else {
            match self.elsekey {
                Some(x) => x.run(self,keys),
                None => {
                    echo(keys);
                    self.current = self.current.clone() + str::from_utf8(keys).unwrap();
                    true
                }
            }
        }
    }

Dodałem tylko 'a przed mut self, Drugi parametr nie ma żadnego parametru czasu życia. Następujące też nie pomaga:

    fn accept<'b>(&'a mut self, keys : &'b [u8]) -> bool {

Oryginalnie było:

fn accept(&mut self, keys : &[u8]) -> bool {
3

Kompilator widząc &'a mut self dochodzi do wniosku, że aby daną funkcję uruchomić, self musi żyć tyle samo ile trzymana w tym selfie referencja - jest to dosyć niecodzienna sytuacja, bo na ogół to referencje wrzucane do struktur żyją dłużej niż te struktury (a nie na odwrót, jak by sugerowało &'a mut self).

Przykładowo, taki kod działa:

struct Foo<'a> {
    val: &'a str,
}

impl<'a> Foo<'a> {
    fn foo(&mut self) {
        println!("{}", self.val);
    }
}

fn main() {
    let mut foo: Foo<'static> = Foo {
        val: "Hello!",
    };
    
    foo.foo();
}

... ale już wprowadzenie pewnej niepozornej zmiany:

impl<'a> Foo<'a> {
    fn foo(&'a mut self) {
        println!("{}", self.val);
    }
}

... kończy się:

error[E0597]: `foo` does not live long enough
  --> src/main.rs:16:5
   |
12 |     let mut foo: Foo<'static> = Foo {
   |                  ------------ type annotation requires that `foo` is borrowed for `'static`
...
16 |     foo.foo();
   |     ^^^^^^^^^ borrowed value does not live long enough
17 | }
   | - `foo` dropped here while still borrowed

Problem wynika z tego, że kompilator widząc foo.foo(); sprawdza bounds wymagane do odpalenia danej funkcji (tj. sprawdza czy uruchomienie jej jest legalne w danym kontekście) - tutaj widzi, że do uruchomienia .foo() trzeba mieć &'a mut self, a z kontekstu dostrzega, że 'a == 'static; ergo - potrzebujemy &'static mut self; no i tutaj pies pogrzebany, bo oczywiście zmienna lokalna nie może zostać zborrowowana jako 'static.

(przy czym 'static sam w sobie nie gra tutaj większej roli - to samo zjawisko można odtworzyć z wykorzystaniem osobnej funkcji.)

Wersja bez explicit lifetime'ów działa, ponieważ jest ona elidowana w trochę inny sposób, do:

impl<'a> Foo<'a> {
    fn foo<'b>(&'b mut self)
    where
        'a: 'b,
    {
        println!("{}", self.val);
    }
}

... gdzie jedynym wymogiem jest to, aby 'a (tj. coś wrzucone w strukturę) żyło dłużej niż 'b (tj. struktura sama w sobie); co ma sens, bo w końcu Foo (jako "kontener", 'b) jest tworzone zawsze po referencji ('a), więc siłą rzeczy będzie żyło dłużej.

(w porównaniu do &'a mut self, można to sobie wyobrazić jako nie ma potrzeby, aby biblioteka żyła tyle samo ile znajdujące się w niej książki.)

W ramach ekstra ciekawostki dorzucę, że wersja z &'a self (zamiast &'a mut self), być może zaskakująco, działa:

struct Foo<'a> {
    val: &'a str,
}

impl<'a> Foo<'a> {
    fn foo(&'a self) {
        println!("{}", self.val);
    }
}

fn main() {
    let foo: Foo<'static> = Foo {
        val: "Hello!",
    };
    
    foo.foo();
}

... a wynika to z mechanizmu wariancji - jest to dosyć złożony temat (którego nie warto zgłębiać na początku, ale przydaje się później), ale tl;dr:

  • każdy lifetime niesie ze sobą pewną ekstra informację mówiącą o tym, czy w miejsce danego lifetime'u można podrzucić inny lifetime (np. mniejszy albo większy),
  • wewnątrz &'a T, 'a jest kowariantne (tj. pozwala ono na wykorzystanie innego lifetime'u w miejsce 'a),
  • wewnątrz &'a mut T, 'a jest inwariantne (tj. nie pozwala ono na wykorzystanie innego lifetime'u w miejsce 'a),
  • stąd mając &'a mut self (gdzie 'a = static), kompilator wymaga &'static mut self, ale już &'a self pozwala na auto-reborrow i podstawienie w miejsce 'a lifetime'u krótszego, zgodnego z cyklem życia zmiennej lokalnej foo; cała ta mechanika istnieje po to, aby nie dało się wrzucić czegoś żyjącego krócej w coś żyjącego dłużej.

Jak wspomniałem, trafiłeś na dosyć niecodzienny problem - pytaj śmiało i wytykaj palcami, jeśli coś jest nadal niejasne :-)

2

Ja jeszcze od siebie dodam zajebisty IMO talk o lifetime i jak to działa https://m.youtube.com/watch?v=rAl-9HwD858

Oraz wariancje o których wspomina @Patryk27 https://m.youtube.com/watch?v=iVYWDIW71jk

Ogólnie polecam pana, na jego stronie można też dodawać tematy/głosować na już dodane by zrobił o tym stream.

0

Dzięki wielkie za materiały i wyjaśnienia. Teraz wiem w czym rzecz. Trochę na ślepo próbowałem te lifetimy robić, a rzecz była w tym, że x.run(self, keys) odnosi się do funkcji typu fn run (&self, tr: &'a mut TermReader<'a>, keys: &[u8]) -> bool (i dalej jeszcze handler, wskaźnik na funkcję tego samego prototypu), co powiela błąd ze złym typem. Zrozumiałem, że wystarczy rozebrać strukturę, która jest przeładowana (brzydki nawyk z C):

struct TermReader<'a> {
    key_map : KeyBind<'a>,
    elsekey : Option<KeyAction<'a>>,
    current : String,
    hint_gen : ShCommands,
    hints : Option<ExcerptIter<'a, String>>, // <- ten iterator wymaga lifetime'a
    pub args : Vec<String>                   // Pozostałe wynikaja z tego, że prototyp handlera
}                                            // musi je uwzględnić

Rozbiję sobie to na 2,3 struktury i handlery będę mieć osobno. To powinno uprościć zależoności i rozwiązać problem. Tylko z ciekawości dopytam, czy choć teoretycznie jest możliwe zaimplementowanie takiego handlera, bez dzielenia tego na osobne struktury (oczywiście nie używając unsafe). Chyba też możliwym rozwiązaniem byłoby użycie Rc zamiast referencji?

1

Hmm, a nie wystarczyłoby fn run<'a, 'b>(&self, tr: &'a mut TermReader<'b>, keys: &[u8]) -> bool?

0

O, nawet udało mi się usunąć problem z lifetimem, trzeba było tylko typ handlera zdefiniować jako for <'a, 'b> fn(&'b mut TermReader<'a>, &[u8]) -> bool;… Tylko wciąż dostaję błąd:

error[E0502]: cannot borrow `tr.args` as immutable because it is also borrowed as mutable
   --> src/main.rs:177:20
    |
176 |     reading(&mut input, &mut tr);
    |                         ------- mutable borrow occurs here
177 |     println!("{}", tr.args.join(","));
    |                    ^^^^^^^^^^^^^^^^^
    |                    |
    |                    immutable borrow occurs here
    |                    mutable borrow later used here

Tego trochę nie czaję, bo tr przecież nie jest referencją… Chyba, że println! robi z niego referencję?

No i jeszcze jedna rzecz się nie kompiluje:

fn ac_elsekey<'a, 'b>(tr: &'b mut TermReader<'a>, keys: &[u8]) -> bool
        where 'a : 'b {
    if tr.args.len() > 0 {
        echo(keys);
        tr.current = tr.current.clone() + str::from_utf8(keys).unwrap();
        return true
    }

    let trial = tr.current.clone() + str::from_utf8(keys).unwrap();
    match tr.hint_gen.for_prefix(&trial) {
        Ok(mut newhints) => { 
            if newhints.len() > 0 {
                let first = newhints.get().unwrap();
                Term.echo(first.get(tr.current.len()..).unwrap().as_bytes())
                    .endline()
                    .hmove(-((first.len() - tr.current.len() - 1) as i32));
                
                tr.current = tr.current.clone() + str::from_utf8(keys).unwrap();
                tr.hints = Some(newhints);
            }
        },
        Err(_) => {}
    }

    true
}

Wali błędem:

error[E0623]: lifetime mismatch
   --> src/main.rs:152:28
    |
134 | fn ac_elsekey<'a, 'b>(tr: &'b mut TermReader<'a>, keys: &[u8]) -> bool
    |                           ----------------------
    |                           |
    |                           these two types are declared with different lifetimes...
...
152 |                 tr.hints = Some(newhints);
    |                            ^^^^^^^^^^^^^^ ...but data from `tr` flows into `tr` here

i do tego

error[E0308]: mismatched types
   --> src/main.rs:201:71
    |
201 |     let mut tr = TermReader::new(initial_keys, Some(KeyAction::Action(ac_elsekey)));
    |                                                                       ^^^^^^^^^^ one type is more general than the other
    |
    = note: expected fn pointer `for<'a, 'b, 'r> fn(&'b mut TermReader<'a>, &'r [u8]) -> _`
               found fn pointer `for<'r> fn(&mut TermReader<'_>, &'r [u8]) -> _`

Czego nie rozumiem, bo KeyAction::Action jest zdefiniowane tak:

type KAHandler = for <'a, 'b> fn(&'b mut TermReader<'a>, &[u8]) -> bool;

#[derive(Clone,Copy)]
enum KeyAction {
    Action(KAHandler),
}
1

Żeby rozwiązać problem trzeba było zrezygnować z mutowalnej referencji na TermReader i zamiast tego przejmować własność i zwracać nowy obiekt, bo inaczej w pętli Ok(len) => tr.accept(&cfg, &buff[0..len]), jest interpretowane jako wielokrotne pożyczenie do zapisu. Brzmi rozsądnie, mniejsza o to czemu wcześniej tego nie było. Ostatecznie rozbiłem TermReader na dwie struktury:

struct TermCfg {
    key_map : KeyBind,
    elsekey : Option<KeyAction>,
    hints : ShCommands,
}

// ...

struct TermReader<'a> {
    current : String,
    pub args : Vec<String>,
    chint : Option<ExcerptIter<'a, String>>
}

Funkcja accept ma wtedy taką formę:

    pub fn accept<'b>(self, cfg : &'b TermCfg, keys : &[u8]) -> (bool, TermReader<'b>)
            where 'a : 'b {
        if cfg.key_map.contains_key(&keys[0]) {
            let x = cfg.key_map[&keys[0]];
            x.run(self, cfg, keys)
        } else {
            match cfg.elsekey {
                Some(x) => x.run(self, cfg, keys),
                None => {
                    echo(keys);
                    let nc = self.current.clone() + str::from_utf8(keys).unwrap();
                    (true, self.with_current(nc))
                }
            }
        }
    }

Odpowiednio więc:

type KAHandler = for <'a> fn(TermReader<'a>, &'a TermCfg, &[u8]) -> (bool, TermReader<'a>);

Natomiast wywoływane to wszystko jest tak:

fn reading(input : &mut dyn Read, cfg : TermCfg) -> Vec<String> {
    let mut buff : [u8;10] = [0;10];
    let mut tr = TermReader::new();

    while match input.read(&mut buff) {
        Ok(len) => { let (cont, ntr) = tr.accept(&cfg, &buff[0..len]);
                     tr = ntr;
                     cont},
        Err(e) => {
            println!("ERROR: {}", e);
            false
        }
    } {}

    tr.args
}

I się kompiluje. ;)

1 użytkowników online, w tym zalogowanych: 0, gości: 1