Killing `Ok(())` with one-line functions

There have been many proposals about ways we could get rid of Ok(()) at the end of functions that return a Result<(), Error>:

fn do_stuff() -> Result<(), Error> {
    let foo = foo()?;
    bar(foo)?;
    let baz = baz(None)?;
    foo_bar_baz(foo, baz)?;
    Ok(())
}

Recently, I came across a piece of code that did this:

fn do_stuff() -> Result<(), Error> {
    // ...
   Ok(unsafe {
       let unsafe_thing = make_unsafe_thing();
       do_some_unsafe_thing(unsafe_thing);
   })
}

Using the implicit () that the unsafe block evaluates to to create the Ok(()) return value without having to add a separate line, which seemed a lot nicer (unfortunately clippy complains). So I realized that if we had the hypothetical one-line functions that have been floated around a lot for try, async, etc., we could remove the need for Ok(()) while still explicitly writing Ok:

// one line functions: fn foo() -> Foo = foo();

fn do_stuff() -> Result<(), Error> = Ok({
    let foo = foo()?;
    bar(foo)?;
    let baz = baz(None)?;
    foo_bar_baz(foo, baz)?;
})

This would work with non-unit return values as well:

fn do_stuff() -> Result<Foo, Error> = Ok({
    foo()
})

There has also been a lot of support for using the implicit Ok wrapping of try blocks for this:

fn do_stuff() -> Result<Foo, Error> = try {
    foo()
}

I think I prefer the Ok({ version because I don't like implicit wrapping in general, but curious what other peoples thoughts are?

I'm not sure if I like it, but can't we teach rustfmt this?

fn foo() -> Result<(), ()> {Ok({
    foo()?;
})}

This is valid rust today, and avoids the waterfall indent

(I am explicitly not saying anything about the core proposal.)

The fact that try { value } does success wrapping such that try { value? } is a no-op (modulo potential error type conversion) is a decided fact, and very unlikely to change. (To the point that it would basically require "ok wrapping" in this position to be completely unworkable.)

3 Likes

Yes, it would work cool with one-line functions, but at this point I don't expect them to be added, nor would I want to. It would impose a significant churn of idioms in the ecosystem with next to no real benefit.

With regards to wrapping a block in an Ok, I have tried a similar pattern in various places. In general, my net opinion of that pattern is lukewarm at best. It just feels odd and needlessly complicated. It requires keeping an extra level of nesting at all times even though it's not required by the logic. At this point I much prefer the body of the function to be as linear as possible, with early returns on exceptional conditions. The wrap-everything-in-Ok pattern just doesn't mix very well with that approach.

I also find it confusing to have early returns and error returns within an Ok-wrapped expression. It feels like betrayal: you start the line telling me everything is Ok, and then gradually introduce subtle footnotes which invalidate that promise. I much prefer when the code inside of Ok is really infallible, and preferably as simple as possible.

Finally, I don't think Ok(()) is a problem which needs solving. It's a very simple idiom which is trivial to read and write, it's impossible to mess up, and it's consistent with the rest of the language. Pretty much every workaround would be worse on at least some of those metrics.

8 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.