rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
96.66k stars 12.49k forks source link

Tracking issue for error source iterators #58520

Open sfackler opened 5 years ago

sfackler commented 5 years ago

This covers the <dyn Error>::iter_chain and <dyn Error>::iter_sources methods added in #58289.

haraldh commented 3 years ago

So, if nobody objects, I will open a PR with the "Remove chain() and add sources() instead" solution, as nothing else provides an ergonomic solution.

haraldh commented 3 years ago

Here we go: https://github.com/rust-lang/rust/pull/81705

KodrAus commented 3 years ago

I think @haraldh touched on a reason to consider both a method that iterates over self and its sources and one that just includes its sources: you can call the latter on any error, but can only call the former on an error you can cast into dyn Error + 'static:

// Valid for any `&E: Error + ?Sized`
fn sources(&self) -> Chain<'_> {}

// Valid only for `&E: Error + Sized + 'static`
fn chain(&self) -> Chain<'_> where Self: Sized + 'static {}

So you could write a maximally compatible method to display an error using sources:

fn render(err: impl Error) {
    fn render_inner(err: impl Error) { .. }

    render_inner(&err);

    for source in err.sources() {
        render_inner(source);
    }
}

but you couldn't write that same implementation using chain unless you also constrain the input error to 'static.

You could argue that's a bit of a forced difference though, because in practice I think non-'static errors aren't very useful anyway since they can't participate in the source chain, but currently unless you wrap a Box<dyn Error + 'static> up into a newtype that implements Error the only impl Error you can get from it is non-'static.

notgull commented 3 years ago

Shouldn't the iterator proper implement FusedIterator?

dtolnay commented 3 years ago

Shouldn't the iterator proper implement FusedIterator?

That's not obvious. There is no documented requirement on https://doc.rust-lang.org/1.53.0/std/error/trait.Error.html#method.source that it behave in a fused way.

notgull commented 3 years ago

Shouldn't the iterator proper implement FusedIterator?

That's not obvious. There is no documented requirement on https://doc.rust-lang.org/1.53.0/std/error/trait.Error.html#method.source that it behave in a fused way.

Apologies if I'm misinterpreting something, but once the Error object returns a None, that's the end of the error chain, isn't it? You can't get another &(dyn Error + 'static) out of that. I guess you could create an infinite loop, but ending is not required for fused iterators.

mathstuf commented 3 years ago

Nothing says the error can't change its mind about what its source is:

impl Error for SillyError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        if rand::int() == 1 { Some(&self.source) } else { None }
    }
}
notgull commented 3 years ago

Nothing says the error can't change its mind about what its source is:

impl Error for SillyError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        if rand::int() == 1 { Some(&self.source) } else { None }
    }
}

Is it a requirement for fused iterators to not involve randomness? All I'm saying is that std::error::Chain could implement FusedIterator, since, unless there are plans to change the way that it is implemented, it could get a marginal win in the albeit rare case where one passes it into a function that fuses it.

mathstuf commented 3 years ago

Is it a requirement for fused iterators to not involve randomness?

I guess my example might still not be enough as std::error::Chain will assume it's over. The rule for FusedIterator is that once None is returned, None will always be returned on subsequent .next() calls. I suppose whether the last Error is delegated to or if there is some implicit FusedIterator assumption in std::error::Chain would answer this question.

nrc commented 2 years ago

In #100955, I renamed chain to sources, it still includes self in the iterator. I added a comment to try and record the issue with why source must be defined in the impl and not the trait. In terms of solutions, this should be fixed by the dyn* work which will permit more methods on trait objects. The other possible solution is to remove self from the iterator (this is a solution because the problem is in converting self to a trait object, which might not work if self is itself a wide pointer which is not a trait object, all the other sources are already trait objects so skipping self means no conversion has to happen). Adding an Unsize bound is not a solution because its not backwards compatible.

jonhoo commented 1 year ago

I recently discovered that io::Error doesn't return custom inner errors through Error::source (https://github.com/rust-lang/rust/issues/101817). I wonder whether we should work around that for iter_chain and iter_sources so that they know how to walk through (and continue after) io::Error::Custom as well?

zopsicle commented 5 months ago

Solutions to the un-ergonomic usage

Another solution is to make sources a function in the module core::error, rather than a method on dyn Error. One would write use std::error; and then error::sources(&error).