Can't to introspect error cause


#1

I have an error e and want to figure out if the root cause was an IO error and, if it is, do something (e.g., exit with the raw error code):

use std::io;

fn run() -> Result<(), Box<std::error::Error>> {
    /* ... */
}

fn main() {
    match run() {
        Ok(_) => {}
        Err(e) => {
            let mut e = &*e as &std::error::Error;
            while let Some(cause) = e.cause() {
                e = cause;
            }
            if let Some(io_e) = e.downcast_ref::<io::Error>() {
                // ...
            }
        }
    }
}

Unfortunately, e.cause() returns an &std::error::Error not an &(std::error::Error + 'static) so I can’t call downcast_ref() on it. Is there any way to work around this? Not being able to reflect on the error chain is pretty annoying.

The error:

tmp.rs:11:26: 11:28 error: `*e` does not live long enough
tmp.rs:11             let mut e = &*e as &std::error::Error;
                                   ^~
tmp.rs:11:26: 11:28 note: reference must be valid for the static lifetime...
tmp.rs:8:5: 19:6 note: ...but borrowed value is only valid for the match at 8:4
tmp.rs:8     match run() {
             ^
error: aborting due to previous error

Also, does anyone know why Any: 'static?


#2

@aturon, given that you wrote the RFC, any thoughts?


#3

@brson You love errors… Have you run into this problem (and is there a (unsafe?) way to work around it)?


#4

@stebalien No that is the first time I’ve noticed this. It is horrible :slight_smile: I filed an issue: https://github.com/rust-lang/rust/issues/35943


#5

This I do know. Lifetimes are “erased”, which means that we don’t have any data about them at execution time, so we can’t check if your downcast is correct or not.


#6

Yes, this definitely seems like a bug!


#7

Is there any way to check if the type is correct for some lifetime? I believe this would allow calling downcast_ref as long as the target type is covariant (just shrink the lifetime) or static.


#8

What one could imagine, I think, would be allowing a downcast so long as the target type has no lifetime parameters (basically). Which would mean that the input must be 'static. We have no way to express that right now though (it’s not quite what T: 'static means…though it would imply that T: 'static).


#9

I was hoping you could take advantage of the fact that the lifetimes are erased but I may be misunderstanding what you mean by that. That is, erase the lifetimes on both sides and fill them back in with some minimal lifetime (i.e., less than the minimum lifetime of the &Any).


#10

I was initially thinking something similar, but I believe there are unfortunately some variance issues with simply shortening lifetimes - if you had a, for example, HashSet<&'static str> in an &mut Any, and could get a &mut HashSet<&'a str> for some 'a shorter than 'static, you could insert strings that don’t live long enough.


#11

Yes but what if this were only allowed for covariant types?


#12

Can that be expressed in the type system? Seems like it’d require some higher kinded bounds?


#13

No, it would require a Covariant marker trait (which might actually be a good idea for asserting covariance but that’s another issue) to do this in the type system.

I was under the impression that Any was more magical as it is and could just do this with some compiler help (return None if the target isn’t covariant). However, that would probably be too magical and would likely confuse users.


#14

There are basically no types that are covariant in lifetimes. That would mean that it is sound to make the lifetime bigger than it used to be – which is very unusual. In fact, we’ve been discussing removing covariance for lifetimes altoghether, in order to address a long standing safety hole.


#15

What’s the rule that allows me to treat &'a T as &'b T for all 'b: 'a? I thought that was covariant lifetimes…


#16

Well, there are some differences in terminology. But I call that contravariance – &'a T <: &'b T if 'b <= 'a (which is the same as saying 'a: 'b). Put another way, with contravariance, you can safely make lifetimes smaller (with covariance, you can safely make them bigger). The point is that if you have some type that is covariant (the unusual case) in a lifetime, then we could safely cast that to the same type but using 'static. Regardless, it’s certainly true that the Any: 'static rule is stricter than necessary, but defining a more precise rule would require extending the language in various special purpose ways.

Really the problem here is that the Error trait definition is broken. :frowning: But I’m not sure how to fix it. Hence https://github.com/rust-lang/rust/issues/35943.


#17

Really the problem here is that the Error trait definition is broken. :frowning: But I’m not sure how to fix it. Hence https://github.com/rust-lang/rust/issues/359432.

I completely agree. I’m just trying to find a hack to make this work.