Why does PoisonError implement Error?

I learned the hard way and then found this comment by dtolnay:

Regarding PoisonError, that is not intended to be stored in an error variant. It signals that some other thread panicked while holding the same Mutex or RwLock and that the current thread should either recover (via into_inner) or panic also (via unwrap). Either way the PoisonError should never end up passed around in return types.

The following does not compile, because of a lifetime issue:

use std::error::Error;
use std::sync::Mutex;

fn main() {
    match fn1() {
        Ok(val) => println!("Value {}", val),
        Err(e) => println!("Error {}", e),
    }
}

fn fn1() -> Result<i32, Box<dyn Error + 'static>> {
    let mutex = Mutex::new(42);
    let val = mutex.lock()
        ?;
        //.unwrap();
    Ok(*val)
}

But why does PoisonError then implement the Error trait?
I thought that the idea of implementing this trait is that it can be stored in an error variant.

Seems like it’s been that way since pre-1.0, and it didn’t have much discussion at the time — that was also long before we figured out what we wanted to do with Error. We might not have implemented it if we were adding PoisonError today, but it’s not like we can remove it now.

I'm not sure if you could not remove it.
If you still implement the functions in Error (description, source, cause, provide) directly in
impl PoisonError I wouldn't know what would break, given the fact trying to put a PoisonError in a Box doesn't work already.

The current error message for the code above is:

error[E0515]: cannot return value referencing local variable `mutex`
  --> src/main.rs:22:15
   |
22 |     let val = mutex.lock()?;
   |               -----^^^^^^^^
   |               |
   |               returns a value referencing data owned by the current function
   |               `mutex` is borrowed here

which left me wondering why mutex was borrowed there.

Without implementation of the Error trait, the error message would be:

error[E0277]: the trait bound `PoisonError<MutexGuard<'_>>: std::error::Error` is not satisfied
  --> src/main.rs:22:15
   |
22 |     let val = mutex.lock()?;
   |                           ^ the trait `std::error::Error` is not implemented for `PoisonError<MutexGuard<'_>>`
   |
   = help: the following other types implement trait `FromResidual<R>`:
             <Result<T, F> as FromResidual<Yeet<E>>>
             <Result<T, F> as FromResidual<Result<Infallible, E>>>
   = note: required for `Box<dyn std::error::Error>` to implement `From<PoisonError2<Mutex2Guard<'_>>>`
   = note: required for `Result<i32, Box<dyn std::error::Error>>` to implement `FromResidual<Result<Infallible, PoisonError<MutexGuard<'_>>>>`

Would have been much clearer to me.

You can do more with Error than just return them as Box<dyn Error>.

But it does work, it is only extremely hard to do and should be avoided. PoisonError carries around the created MutexGuard which references the Mutex internally. This compiles fine, it's just terrible because of the reference to Mutex inside your dyn Error:

use std::error::Error;
use std::sync::Mutex;

fn main() {
    let mutex = Mutex::new(42);
    
    match fn1(&mutex) {
        Ok(val) => println!("Value {}", val),
        Err(e) => println!("Error {}", e),
    }; // <- semicolon here so that the MutexGuard is guaranteed to be dropped before the Mutex
}

fn fn1<'a>(mutex: &'a Mutex<i32>) -> Result<i32, Box<dyn Error + 'a>> {
    let val = mutex.lock()?;
    Ok(*val)
}

Playground.

3 Likes

Thanks for the replies. Much appreciated by a relative Rust rookie.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.