How to use operator "?" with shared mpsc?

Viewed 102

I am struggling to apply the knowledge I received in the chapter The ? Operator Can Be Used in Functions That Return Result to my code with shared mpsc channel.

Here is minimal code:

use std::sync::{ mpsc, Arc, Mutex };
use std::error::Error;

fn main() 
{
}

fn sh_rx_get( sh_rx : Arc< Mutex< mpsc::Receiver< usize > > > ) -> Result< usize, Box<dyn Error> >
{
  let rx = sh_rx.lock()?;
  let r = rx.try_recv()?;
  Ok( r )
}

The error I got:

error[E0515]: cannot return value referencing function parameter `sh_rx`
  --> src/main.rs:10:12
   |
10 |   let rx = sh_rx.lock()?;
   |            -----^^^^^^^^
   |            |
   |            returns a value referencing data owned by the current function
   |            `sh_rx` is borrowed here

Online playground.

Any suggestion or hint?

1 Answers

The reason for this error is that std::sync::Mutex::lock() returns a Result<MutexGuard<'_, T>, PoisonError<MutexGuard<'_, T>>>. As you can see, the error case of that Result carries a lifetime that borrows the original lock (the lifetime '_ in MutexGuard). The compiler is trying to tell you that returning an error via the ? would return a borrowed value - PoisonError<MutexGuard<'_, T>>> - that was created, and is only valid, in this function; this would create a dangling reference and is therefor a compile error.

The offending local borrow ("data owned by the current function") is created implicitly when sh_rx.lock() is called. Rust will automatically reference the Arc sh_rx and then dereference from Arc to the Mutex, where .lock() is actually defined.

The reason lock() returns a value that borrows the lock even in the error case is that locking fails if another thread panicked while holding the lock; this could mean that the data protected by the lock is in an inconsistent state. PoisonError has an into_inner() method which acquires the lock regardless of this, and it requires the a borrow of the original lock to do so.

You can fix this by converting the PoisonError into some other type which does not borrow the lock. For example:

let rx = sh_rx.lock().map_err(|e| e.to_string())?;

This works because there is a From<String> for Box<dyn Error> in std, which is automatically used by the ?-operator to convert the String into the Box<dyn Error> which your function returns.

Related