I have to say that's the first time I've heard someone say Rust doesn't have enough return types. Idiomatically, possible error conditions would be wrapped in a Result. `foo()?` is fantastic for the cases where you can't do anything about it, like you're trying to deserialize the user's passed-in config file and it's not valid JSON. What are you going to do there that's better than panicking? Or if you're starting up and can't connect to the configured database URL, there's probably not anything you can do beyond bombing out with a traceback... like `?` or `.unwrap()` does.
For everything else, there're the standard `if foo.is_ok()` or matching on `Ok(value)` idioms, when you want to catch the error and retry, or alert the user, or whatever.
But ? and .unwrap() are wonderful when you know that the thing could possibly fail, and it's out of your hands, so why wrap it in a bunch of boilerplate error handling code that doesn't tell the user much more than a traceback would?
`?` (i.e. the try operator) and `.unwrap()` do not do the same thing.
It’s odd that the .unwrap() hack caused a huge outage at Cloudflare, and my first reaction was “that couldn’t happen in Go haha” but… it definitely could, because you can just ignore returned values.
But for some reason most people don’t. It’s like the syntax conveys its intent clearly: Handle your damn errors.
And maybe not quite as standard, but thiserror if you don’t want a stringly-typed error?
As for the example you gave:
File::create("foo.txt")?;
If one added context, it would be File::create("foo.txt").context("failed to create file")?;
This is using eyre or anyhow (common choices for adding free-form context).If rolling your own error type, then
File::create("foo.txt").map_err(|e| format!("failed to create file: {e}"))?;
would match the Go code behavior. This would not be preferred though, as using eyre or anyhow or other error context libraries build convenient error context backtraces without needing to format things oneself. Here's what the example I gave above prints if the file is a directory: Error:
0: failed to create file
1: Is a directory (os error 21)
Location:
src/main.rs:7
The proof is in the pudding, though. In my experience, working across Go codebases in open source and in multiple closed-source organizations, errors are nearly universally wrapped and handled appropriately. The same is not true of Rust, where in my experience ? (and indeed even unwrap) reign supreme.