rust-lang / futures-rs

Zero-cost asynchronous programming in Rust
https://rust-lang.github.io/futures-rs/
Apache License 2.0
5.43k stars 629 forks source link

Stream & Sink Error types could benefit from core::fmt::Debug bound #2857

Open bionicles opened 6 months ago

bionicles commented 6 months ago

I find one quanta of friction making it a bit harder to work with generic streams & sinks is they have this associated error type which is usually awesome because you control the error type, but in the case of streams and sinks, the low level details are really hard, so it's much more likely for a wide variety of developers to wind up using a smaller number of streams and sinks at a high level. Anyway, since more people are using streams and sinks than developing new streams and sinks, that means all the users are stuck with the trait bound on type Error, which doesn't exist,

Problem is, type Error doesn't have any trait bounds, it could be anything; this means it's hard to functionally map the Err branches because we need a custom error handler for each kind of Stream and Sink, you can't debug print your errors from Streams / Sinks, you're screwed, can't turn it into anyhow::Error because it's not a std::error::Error.

One wonders how much of a breaking change it would be for Stream and Sink Error to both be bounded by "std::error::Error" ? big issue is this weirdly breaks with anyhow because they didnt implement std::error::Error for anyhow::Error, a decision which makes no sense except as a hack to avoid From conflicts. Therefore, I meekly suggest we need an easier bound for Stream / Sink errors than std::error::Error, but more than no bound..

A type Error: core::fmt::Debug bound would at least let us print out the errors regardless of the stream/sink we're dealing with. That'd be the minimalist approach and would work for anyhow::Error as well as custom or default error types. I just think it's important to have a fully functional interface because otherwise we have to re-implement differently for each kind of stream (source) and sink

LMK what you think, it's probably going to break stuff, but at least this way we could always generically debug print our error messages in streaming programs. Thank You!

bionicles commented 6 months ago

example of the problem:

        match sink.send(output).await {
            Err(e) => Err(anyhow!("unprintable error from the outpipe sink  {e:#?}")),

you'd need to repeatedly qualify your functions only work

where <S2 as futures::Sink<O>>::Error: std::fmt::Debug
error[E0277]: `<S2 as futures::Sink<O>>::Error` doesn't implement `std::fmt::Debug`
   --> src/flow.rs:148:76
    |
148 |             Err(e) => Err(anyhow!("unprintable error from the outpipe sink {e:#?}")),
    |                                                                            ^^^^^^ `<S2 as futures::Sink<O>>::Error` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug`
    |
    = help: the trait `std::fmt::Debug` is not implemented for `<S2 as futures::Sink<O>>::Error`
    = note: this error originates in the macro `$crate::__private::format_args` which comes from the expansion of the macro `anyhow` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider further restricting the associated type
    |
144 |     async fn load(&mut self, output: O) -> anyhow::Result<()> where <S2 as futures::Sink<O>>::Error: std::fmt::Debug {
    |                                                               ++++++++++++++++++++++++++++++++++++++++++++++++++++++