This is a request for comments in the literal sense, also sorry about it being a bit bikesheddy - it's about showcasing a workflow in the api docs, as well as saving me a few keystrokes.
The proposal is to add
// open question: what about `Send`,` Sync`, not `'static`.
type BoxError = Box<dyn std::error::Error + 'static>;
and in the documentation explain how you can return Result<T, BoxError> and lots of things like std::io::Error will coerce to it. For example
fn lots_of_different_errors() -> Result<(), BoxError> {
let file_contents = fs::read_file_contents("some/path")?;
let parsed = serde_json::from_str(&file_contents)?;
let different_object = parsed.try_into()?;
// This also may return BoxError
do_something(different_objects)
}
It shows how you can do run-time polymorphism with arguably its most useful example - Errors.
Pros
Saves some keystrokes
Standardizes the name for Box<Error + 'static + etc..>, and also standardizes which auto traits it implements
Gives a place in the API docs to explain the trait object error pattern
Easy/low risk to implement
Cons
Doesn't add that much and increases surface area
Forces people to use a specific combination of 'static + Send + Sync
Anyway just putting it out there to see what people think.
Yes yes yes. Not to mention that most of the time it's very nice or even required that an error type be E: Send + Sync, most notably in the case of compiler-synthesized, Result-returning #[test] functions, which run on many threads.
I actually place a type Error = Box<dyn std::error::Error + Send + Sync> at the top of little programs quite often, if I don't need the full bloat of failure. I often now prefer error-derive too.
So I'm in favor (and I prefer the Box one over an opaque type, but it would be nice if few rough edges around dynamic dispatch were improved too ‒ for example, such Error doesn't implement std::error::Error, which bites around generic code).
Anyway, using BoxError as the name kind of uses the name of implementation detail. I'd prefer having it be named after the purpose, so what about AnyError?
Also, I'd strongly prefer having Send + Sync there. Some libraries return Box<dyn Error> and working with that is problematic even though the all wrapped errors are in fact Send and Sync. So I think having errors them Send + Sync should be the default.
I'm inclined to agree with you about including Send + Sync: we aren't forcing anyone to use this, they can continue to define their own type alias like type Error = Box<dyn std::error::Error + 'static> if they don't want Send + Sync.
Suppose someone knows very little about rust but has heard that the csv crate can work with very large csv files fast. They go to the documentation and see:
fn example() -> Result<(), Box<dyn Error>> {
// Build the CSV reader and iterate over each record.
let mut rdr = csv::Reader::from_reader(io::stdin());
for result in rdr.records() {
// The iterator yields Result<StringRecord, Error>, so we check the
// error here.
let record = result?;
println!("{:?}", record);
}
Ok(())
}
If instad they see
fn example() -> Result<(), AnyError> {
// Build the CSV reader and iterate over each record.
let mut rdr = csv::Reader::from_reader(io::stdin());
for result in rdr.records() {
// The iterator yields Result<StringRecord, Error>, so we check the
// error here.
let record = result?;
println!("{:?}", record);
}
Ok(())
}
then they don't have to know about Box and the heap, and also dyn and trait objects, allowing them to focus more on the important part of the example.
The big problem is the same as with Box<Error> and failure::Error: they can't implement both From<E: Error> and std::error::Error, which is probably a deal breaker for putting this in std. failure works around this with an awkward Compat type.
If this is happening, I would strongly advocate for going for an implementation close to failure::Error, notably (in addition to the already mentioned opaque thin pointer) , make DynError automatically capture a backtrace.
Personally I'm very much in favor of this. It would give us a standard dynamic error type that can be used in docs, examples, in code that doesn't require typed errors, ... and that captures backtraces for easier debugging.
It would be a big boon for for new users : you can tell them to just use DynError until they gain a better understanding of what error types to use, and they get backtraces automatically.
The danger is of course a proliferation of DynError in contexts where it is not appropriate, eg in libraries. That could hopefully be somewhat countered with documenation.
Is this something that would be solved by speicalization? I'm trying to get my head around the problem, is it to do with the fact that implementing both From<E: Error> and Error could lead to situations where the compiler would make a From<From<From<From<From<...>>>>?
How comfortable would we feel having the stdlib use specialization for this? Since the stdlib can use unstable features on stable, this would let us get the advantages of failure::Error / fehler::Exception in both lib and bin crates with full interop (unlike those crates). That would be a huge advantage for moving this proposal forward in my mind.
The current rule for specialization in the standard library is that the API surface must be fully expressible without specialization. (That is, every specialized impl must be fully covered by the general impl.)
The current rule for specialization in the standard library is that the API surface must be fully expressible without specialization. (That is, every specialized impl must be fully covered by the general impl.)
What specialization would you use for DynError ?
From earlier in the thread
The big problem is the same as with Box<Error> and failure::Error : they can't implement both From<E: Error> and std::error::Error , which is probably a deal breaker for putting this in std. failure works around this with an awkward Compat type.
From my experience with failure::Error, a boxed Error seems limited in use to just applications without specialization but isn't obviously so, creating a trap for developers to make mistakes.
Note that Try's design isn't finalized, so there's also the opportunity here to choose a different ? desugaring that would all us to bypass this kind of problem.