For functions that don't return any type of Result<_>, the ? operator could be used as a sugar for unwraping.
So for example,
fn test() {
some_func()?;
}
could be
fn test() {
some_func().unwrap();
}
since the function test() doesn't return a result.
I think this could be a great use for the ? operator, as typically, either a function returns a result, in which case the error should (usually) be propagated upwards, or the function doesn't and instead unwraps anything it needs.
I don't like this. This hides a panic, which isn't that good, panics should stand out. Also it makes refactoring more error prone because you can change the function from returning a Result to something else and not realize that you aren't handling some errors correctly.
I think that the ? operator does quite a good job of standing out and saying "hey there could be an error here!" The main motivation behind this is that currently, unwrap is too verbose, and it can be very easy to write code that calls a function, unwraps, calls, unwraps, etc, and we get that it can panic there.
I think this use of the ? operator is in fact, very consistent when it comes to reading code, since always ? will mean this code can error. The way the error is handled might be different, but ? will still tell the developer that an error could occur.
As for refactoring:
First, I wanna say that you're definitely right that it's more error prone going from returning result to returning something else. But, going the other way around, the migration from returning something else to returning maybe a Result<_, Box<Error>> (or any other standard/unifying result type) would be extremely easy and not error prone at all. This would encourage people to return Results, and in most cases they should.
Remember that "going from returning result to returning something else" can be done accidentally in a function body without changing the function signature. Consider code like this:
fn test() {
let foo: Option<Foo> = some_func();
let bar: Option<Bar> = foo.map(|value| value.try_bar()?);
}
This would silently resolve the ? to .unwrap(), where currently it would fail to compile because the function argument to Option::map mustn't return a Result. I think this is a pitfall much worse than the small convenience gain.
I'm not sure if I understand your example, but wouldn't we want the code above to desugar to the code below?
fn test() {
let foo: Option<Foo> = some_func();
let bar: Option<Bar> = foo.map(|value| value.try_bar().unwrap());
}
I think it makes a lot of sense that the ? would desugar into an unwrap in that case.
But I can see a case where this would be really really bad:
fn test() -> Result<_, _> {
let foo: Option<Foo> = some_func();
let bar: Option<Bar> = foo.map(|value| value.try_bar()?);
}
It would be reasonable to read that code and think any error from value.try_bar() gets propagated to the test function, but of course it doesn't and the ? would instead would desugar into an unwrap.
I still think it would be a good idea to have some sigil act as syntax sugar for unwrapping, as currently, unwrapping is very verbose, even if it isn't the ? operator (which I now understand can be ambiguous/confusing).
As a general Rust rule of thumb, unwrapping should be very rare. In fact, you don't even need unwrapping at all, since you can just return a Result from main:
fn main() -> Result<(), Error> {
foo()?;
Ok(())
}
And so if you're using unwrapping a lot, I would consider that to be a code smell. There are exceptions where unwrapping is acceptable, but they're rare and ad-hoc.
The problem is even worse in libraries: if a library returns Result you can choose whether to propagate it or unwrap. But if the library unwraps, then you have no choice.
As such, I don't like the idea of making unwrapping more ergonomic, since it goes against good Rust principles, and leads to worse libraries. Instead, Rust programmers should be encouraged to structure their code in order to avoid unwrapping.
If there is some situation where Result is unergonomic (and thus encourages unwrap), we should fix that problem instead.
So, do you have some code which demonstrates a situation where unwrapping is preferred over Result?
I agree. Also, errors can be propagated upwards and unwrapped at the top if needed. Closing.
Most helpful comment
As a general Rust rule of thumb, unwrapping should be very rare. In fact, you don't even need unwrapping at all, since you can just return a
Resultfrommain:And so if you're using unwrapping a lot, I would consider that to be a code smell. There are exceptions where unwrapping is acceptable, but they're rare and ad-hoc.
The problem is even worse in libraries: if a library returns
Resultyou can choose whether to propagate it or unwrap. But if the library unwraps, then you have no choice.As such, I don't like the idea of making unwrapping more ergonomic, since it goes against good Rust principles, and leads to worse libraries. Instead, Rust programmers should be encouraged to structure their code in order to avoid unwrapping.
If there is some situation where
Resultis unergonomic (and thus encourages unwrap), we should fix that problem instead.So, do you have some code which demonstrates a situation where unwrapping is preferred over
Result?