Borrowed value does not live long enough

Вот не понимаю почему, такой код:

pub fn run_blocks(cnc: Rc<RefCell<Controller>>, blocks: Blocks) -> impl Future<Item=(), Error=Error>  {
    let cnc_start = cnc.clone();
    let cnc_load = cnc.clone();
    load(cnc, blocks)
    .and_then(move |(blocks, runtime)| cnc_start.borrow_mut().start().map(|_| (blocks, runtime)))
    .and_then(move |(blocks, runtime)| loop_fn((blocks, runtime), move |(blocks, runtime)| {
            let cnc_load_clone = cnc_load.clone();
            Delay::new(Instant::now() + runtime - Duration::from_secs(1))
            .map_err(|err| panic!("timer failed; err={:?}", err))
            .and_then(move |_| load(cnc_load_clone, blocks).map(|(blocks, runtime)| {
                if blocks.is_empty() {
                    Loop::Break((blocks, runtime))
                } else {
                    Loop::Continue( (blocks, runtime) )
                }
            }))
        }).map(|_| () )
    )
}

вызывает ошибку:

error[E0597]: `cnc_start` does not live long enough
  --> plc/src/lib.rs:78:40
   |
78 |     .and_then(move |(blocks, runtime)| cnc_start.borrow_mut().start().map(|_| (blocks, runtime)))
   |                                        ^^^^^^^^^ borrowed value does not live long enough      - `cnc_start` dropped here while still borrowed
   |
   = note: values in a scope are dropped in the opposite order they are created

and_then - принимает FnOnce и в замыкание передано значение. Что не так?

нарывался на подобное когда пытался максимально близко переписывать на Rust С код.
попробуй заинлайнить
убрав
let cnc_start = cnc.clone() совсем

 load(cnc, blocks)
    .and_then(move |(blocks, runtime)| cnc.clone().borrow_mut().start().map(|_| (blocks, runtime)))

попробовал, та же самая ошибка.
код для экспериментов

Похоже несовершенство borrow checker’а в Rust’2015.
Разбивка цепочки вызовов фиксит проблему

            let res = cnc_start
                .borrow_mut()
                .start();
            res
                .map(move |_| (blocks, runtime))

Еще раз те-же грабли:

use std::sync::{Arc, Mutex};

fn main() {
    let a = Arc::new(Mutex::new(String::new()));
    let b = Arc::new(Mutex::new(String::new()));
    
    match a.lock().and_then(|a| b.lock().map(|b| (a, b))) {
        Ok((a, b)) => {
            println!("a = {}, b = {}", a, b);
        }
        Err(err) => {
            println!("{}", err);
        }
    }
}

Playground

Так работает:

    let tuple = a.lock().and_then(|a|  b.lock().map(|b| (a, b)));
    match tuple {
        //...
    }

Похоже на баг в NLL. Стоит наверно создать ишью.

Хм, будет работать если просто добавить точку с запятой после match.
Баг воспроизводится с одним Arc:

    let a = Arc::new(Mutex::new(String::new()));

    match a.lock() {
        Ok(a) => {}
        Err(_) => {}
    }

Playground

И кстати, в сообщении об ошибке компилятор пишет:

note: The temporary is part of an expression at the end of a block. Consider adding semicolon after the expression so its temporaries are dropped sooner, before the local variables declared by the block are dropped.

Так что это не баг, а фича. :sunglasses:


Судя по всему match создает неявную временную переменную, которая дропается после переменных объявленных в его блоке.
Т.е. происходит что-то вроде этого:

    let t;
    let a = Arc::new(Mutex::new(String::new()));
    t = a.lock();
    match t {_ => {}}

Получим почти тоже самое сообщение об ошибке:

error[E0597]: `a` does not live long enough
 --> src/lib.rs:6:9
  |
6 |     t = a.lock();
  |         ^ borrowed value does not live long enough
7 |     match t {_ => {}}
8 | }
  | -
  | |
  | `a` dropped here while still borrowed

Не знаю что мешает создавать временную переменную как все нормальные люди:

    let a = Arc::new(Mutex::new(String::new()));
    let t = a.lock();
    match t {_ => {}}

Мне не удаётся воспроизвести этот эффект с простыми ссылками. Работает (в смысле выдает ошибку) только с Mutex и RefCell.

И даже с самодельным RefCell (все скопировано из std::cell) ошибка не воспроизводится. А если раскоментировать стандартый RefCell Rust начинает ругаться. :thinking:
Это магия какая-то? Или я что-то делаю не так?

Playground

Нашел что это за магия. Все дело в типаже Drop. Если его заимплементить для MyBorrowRef из примера выше, то самодельный RefCell начинает работать также как стандартный.

Вот пример воспроизводящий эту багофичу без всяких Mutex-ов и RefCell-ов:

struct MyRefHaveDrop<'x>(&'x usize);

impl Drop for MyRefHaveDrop<'_> {
    #[inline]
    fn drop(&mut self) {}
}

fn main() {
    let data = 451;
    match MyRefHaveDrop(&data) { _ => {} }
}

Playground

Структура со ссылкой, для которой реализован Drop, уничтожается позже чем данные на которые она ссылается.
Такой же эффект наблюдается если заменить match на if let.


P.S.: По-моему это очень плохая фича. Она позволяет ломать компиляцию зависимых крейтов. Добавил реализацию Drop к публичной структуре и всё.
Если такое поведение match нельзя исправить, то компилятору стоило бы выдавать ошибку для всех типов независимо от наличия у них типажа Drop.

P.P.S.: Хотя если подумать, то Drop-ом можно сломать вообще все что угодно.

1 лайк