Kudos to the Go team for their process on this. IMHO it's worth reading Russ Cox's explanation of the problem area, including examples, and comparisons to other languages e.g. Rust and Swift.
"But Rust has no equivalent of handle: the convenience of the ? operator comes with the likely omission of proper handling." what's that supposed to mean? The ? operator just bails out if an Error result is returned from the called function, and forwards that Error to the caller. Cleanup is performed implicitly by drop implementations (destructors) using the RAII pattern ala C++.
In the draft design, they give an example of special-case cleanup that would only execute only when an error occurs, not on the success path.
You can emulate this with a boolean flag in your RAII types in Rust or C++, that's set or cleared immediately before a successful return, and then doing conditional logic in your Drop/dtor. Or you could do a std::mem::forget before successful returns. But I guess they think this is an important enough case to dedicate syntax to it for ergonomic reasons, which Rust doesn't have - which is what I think they're getting at.
You can use the match construct for that, and the Result<T,E> type comes with some utility methods that make it easier to clarify your desired semantics in many cases. The page complains that the "match" syntax is clunky, but I'm not that sure how 'handle' is supposed to be better.
As Arnavion points out, handle handles multiple error cases. That said, I'm not convinced it's better in practice. For some comparison points - here's how I'd write the Go CopyFile in Rust:
1) In a real rust codebase you'd probably simply forward std::io::Error instead of converting it into a string like I have here, or give it a better error struct/enum type. I've tried to mimic the Go code here, not fully convert to Rust idioms.
2) You could get rid of the .as_ref() spam by just using &str or &Path to be closer to the Go code, but I'd rather stick at least that close to std::fs::copy's file signature.
3) All explicit close operations are dropped as unnecessary vs the Go code. I guess I could've used std::mem::drop to be more explicit?
4) TempPath is obvious overkill for the single remaining error point
5) In temp-file heavy code you'd probably wrap TempPath + File into TempFile. keep could return the interior File as well.
>1) In a real rust codebase you'd probably simply forward std::io::Error instead of converting it into a string like I have here, or give it a better error struct/enum type.
In all the real code bases I've worked on, there are multiple disparate types of errors that nevertheless have the same context.
Example: A function that takes in a path and parses a config file at that path fails if it can't open the file or if the file is malformed. The file can be malformed because indentation is wrong, because there's a string where there should be an integer, or because a required field is missing. All of these are different error types.
So a single `std::io::Error` is not possible, and erasing them into a `Box<dyn Error>` or wrapping them in a custom (context-containing) type nevertheless requires writing a `.map_err` per each Result value.
`?` uses the `From` trait which means you often don't need `.map_err`. Ignoring crates like error_chain, even the stdlib comes with a From implementation for `Box<dyn Error>` - as long as your error types implement std::error::Error, you shouldn't need an explicit .map_err to box them:
EDIT: That said, adding extra context will often require map_err or similar. But merely type erasing / combining error sources shouldn't need it, unless I'm missing something. (Most of my Rust use so far has been on toy codebases...)
>`?` uses the `From` trait which means you often don't need `.map_err`.
1. `From` is a global solution to a local problem. All `std::io::Error` must necessarily be converted to the same enum variant regardless of what caused them. Failing to open a file and failing to write to a network socket will create the same variant.
2. `From` does not have access to context anyway. A `From<std::io::Error>` is not going to know that the error was hit specifically when "parsing the /etc/foo.conf file", and a `From<std::string::ParseError>` is not going to know the error was hit when "parsing the bar field of the /etc/foo.conf file because it is set to `baz` which is not an integer".
>as long as your error types implement std::error::Error, you shouldn't need an explicit .map_err to box them:
This only works when you want your function to return `Box<dyn Error>` itself without any additional context. The conversation was about needing to add context like in the golang example.
In Rust you could likely make a pair of macros that does this RAII setup. And because you’d explicitly need to import the macro, it’s evident and traceable what’s going on - no Rails-like magic, which the OP rejection is IMO right to avoid.
I think Go sometimes swings too far in the simplicity direction, not letting consenting folks choose to use shorthand, but that’s a very valid design decision.
`handle` would've allowed arbitrary code to execute in case of an error as opposed to "just" returning it back to the caller. That's what he's talking about.
For example, the example returns a custom wrapper around the original error with context that it was a copy operation with such-and-such source and destination. The equivalent in Rust using `failure::Fail::with_context` requires writing the `.with_context(|_| format!("copy: ..."))` on every expression that uses `?` (unless you happen to get lucky and all the inner errors are the same type, so that you can use combinators to combine them into a single `?`-able Result).
Edit: And to be clear, this is not limited to `failure::Fail`. Using your own `enum Error { Copy { source: PathBuf, destination: PathBuf, inner: Box<dyn Error> }, ... }` still requires you to write a manual `.map_err(|err| Error::Copy { ... })` after every Result value that you intend to use `?` on.
https://go.googlesource.com/proposal/+/master/design/go2draf...