Search code examples
rustcompiler-errorsmultiprocessing

Why does one of these work and the other not?


I want to share (and change) a mutable Vec across threads, in a scoped thread pool context, using scoped_thread_pool.

let ids_for_discard: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));

let scoped_outcome = self.threadpool.scoped(|scope| {
    // here I iterate through a Vec of objects...
    for text_doc_hit_obj in &self.handling_framework.text_doc_hit_objs_for_processing {
        // each thread will need its Arc clone...
        let ids_for_discard = Arc::clone(&ids_for_discard);
        scope.execute(move ||  {
            // this (above) closure MUST return "()"... so
            // I use an inner closure to capture errors efficiently, using the "?" operator several times
            let inner_closure = move || -> Result<(), Box<dyn std::error::Error>> {
                ...
                let id_string = text_doc_hit_obj._id.clone();
                // let _ = ids_for_discard.lock()?.push(id_string); // fails to compile
                let _ = ids_for_discard.lock().unwrap().push(id_string); // passes

... it also passes if I use a match clause.

The error I get using the ? operator above is:

error[E0515]: cannot return value referencing local data `ids_for_discard`
   --> src\indexing_pool.rs:112:19
    |
112 | ...                   let _ = ids_for_discard.lock()?.push(id_string);
    |                               ----------------------^
    |                               |
    |                               returns a value referencing data owned by the current function
    |                               `ids_for_discard` is borrowed here

Can anyone explain why ? fails to compile here? I'm really struggling to understand. I wondered whether there might be a danger that an attempt is made to push an Err... but in the event that lock() fails, shouldn't this immediately send control, with this Err, out of the local closure?

NB this also fails:

let id_string = text_doc_hit_obj._id.clone();
let mut vec = ids_for_discard.lock()?;
vec.push(id_string);

Compiler: error[E0515]: cannot return value referencing local data 'ids_for_discard'

Again, if I replace ? with unwrap(), it passes.

I see that the signature of Mutex::lock() is pub fn lock(&self) -> LockResult<MutexGuard<'_, T>>
... and LockResult is pub type LockResult<Guard> = Result<Guard, PoisonError<Guard>>;. Could it be that Box<dyn std::error::Error>> is unable to capture this kind of error???


Solution

  • The PoisonError does contain a MutexGuard and with it a reference to the Arc<Mutex<..>> which in your case is local to the function, so you can't return references to it. You can map_err to exchange the error for something which doesn't contain a reference to any local variable:

    
    use std::sync::Mutex;
    fn err_on_mutex_poisoned() -> Result<(), Box<dyn std::error::Error>> {
        let x = Mutex::new(0);
        // x.lock()?; // doesn't work
        x.lock().map_err(|_| "failed")?; // works
        Ok(())
    }