I could not find this discussed before, so apologies if this was raised before. I find myself writing quite a lot of Ok(()) at the end of functions which return void Result. I was wondering whether the language couldn’t just implicitly insert it in a place where a -> Result<(), Error> function doesn’t have a return value, like:
fun foo() -> Result<(), Error> {
bar()?;
}
Any Error type would be supported. Interested to hear others’ opinions, but will understand if this doesn’t fit in with the language design strategy of the language.
Edit: Note that the last line of the function might not necessarily evaluate to a Result<...>:
Ah, so this does seem to be widely discussed. I can see problems with implicitly wrapping anything else besides (), as it’s not obvious what happens in this case:
fun baz() -> Result<T, Error> {
...
x // x actually is already a Result<T, Error>
}
so I think this small proposal might be more palatable. Either way thanks for pointing me in the right way, very interesting discussion!
mod std::ops;
trait EmptyReturn {
const RETURN: Self;
}
impl EmptyReturn for () {
const RETURN: Self = ();
}
impl<T, E> EmptyReturn for Result<T, E>
where T: EmptyReturn {
const RETURN: Self = Ok(<T as EmptyReturn>::RETURN):
}
with the obvious desugaring: if a block-returning-R’ does not end in an expression, insert <R as EmptyReturn>::RETURN as that final expression. See also for return with no argument.
This should be a constant for what I hope are obvious, spooky-action reasons (as-is this feature is already terrifically spooky).
By the way, in cases where you have several (one or more) Result-returning function calls in a row, you can just omit the final unwrapping and return the return value of the last call directly:
This obviates the need for an explicit Ok(()) at the end. (You could also .map(drop) at the end, if the last call doesn’t return Result<(), MyError>, although not sure if it’s a readability improvement.)
That only works if the final one doesn't need error-conversion, so I prefer doing something that allows all the calls to be consistent in how they look, such as doing
I know, although my note about .map(drop) also applies to .map_err(…). It’s not that I prefer this style; I merely wanted to point out an alternative because some people apparently find Ok(()) annoying. I don’t share that sentiment and I prefer the more uniform style too – my goal was primarily to show how implicit Ok-wrapping and/or special-casing Result<(), _> (both of which I’m strongly against) isn’t really necessary based on OP’s example.
This is not a very good argument, if I may say so. It goes along the lines of “we don’t need X, to replace Y, because there’s a worse way of doing things than Y, sometimes, which is Z”.
This suggestion is about code that already has Ok(()), as the “best” current way of doing things, and how it could be improved. Wrapping the previous line in Ok() or .map(drop) is not a real alternative, imho (that line might not even, and often doesn’t, evaluate to Result<...> - my example is just the simplest piece of code I could come up with to illustrate the scenario). Let me add the example to the post so it doesn’t tempt people to go down this route.
It’s not a “worse way” – some like it, some dislike it, but it’s a minor point. I have an entire line of arguments why Ok-wrapping isn’t a good idea, which I’m definitely not going to reiterate here because it has been discussed in the past several times.
I would like for there to be some way of avoiding Ok(val?) or val.map_err(Into::into) for return expressions, but I’m not sure what that could look like. Not too much of a fan of implicit Ok(()) return, as I think it might end up being too magical.
There is currently the ::candy::fallible! macro to get a dummy but useful auto-wrapping (the only case where it is not great is when wanting to return an explicit Ok(..), since it requires to use an early return to escape the wrapping). But I find it very convenient in general, and allows to split the Ok and Err case return types over two lines
In short, given that our attempts to find more universal coercions have failed, it seems clear that if we are going to do some form of ok-wrapping, it has to have syntactic opt-in.
There was a bunch of exploration of invisible ways to do this, and none were found with which people were happy enough with which to move forward, so I don't think you need to worry about the scenario you raised.