Question about implementation of `std::io::Error::source`

I'm having issues implementing something and don't understand why the implementation of std::io::Error::source is implemented the way it is. Specifically:

    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
        match self.repr.data() {
            ErrorData::Os(..) => None,
            ErrorData::Simple(..) => None,
            ErrorData::SimpleMessage(..) => None,
            ErrorData::Custom(c) => c.error.source(),
        }
    }

why does source for std::io::ErrorKind::Other "skip a level" by returning c.error.source(). Seems like it should return Some(c.errer.as_ref())?

The Display and Debug impl also directly forward to the custom error. If you do io::Error::new(kind, my_error) you aren't creating a new error object which is caused by my_error. Rather the returned io::Error and my_error are one and the same error. This is similar to how anyhow::Error also forwards Error::source() to the respective impls of the error it wraps once you convert it to a Box<dyn Error>.

If you do io::Error::new(kind, my_error) you aren't creating a new error object which is caused by my_error .

But this is exactly what you are doing. Admittedly it's a thin wrapper but from the perspective of the type system you are creating a new object with a new type.

I've figured out that I can work around this by just calling io::Error::into_inner to get the inner error, but this is:

  1. Confusing and non-obvious
  2. Requires an owned value. If you have a reference (which is not Clone) then it can't be done at all
  3. Even with an owned value requires special casing in generic code

Think of this from the perspective of code which prints errors, following the source chain. If it didn't "skip a level", the error message in the io::Error would be printed twice.

The wrapper is irrelevant to the consumer of the error. It's just an implementation detail.