What is the idiomatic way to return an error from a function with no result if successful?

15,571

Solution 1

Use fn do_work() -> Result<(), WorkError>.

Result<(), WorkError> means you want the work to be done, but it may fail.

Option<WorkError> means you want to get an error, but it may be absent.

You probably want the work to be done but not to get an error when you write do_work(), so Result<(), WorkError> is the better choice.

I would expect Option<WorkError> only be used in cases like fn get_last_work_error() -> Option<WorkError>.

Solution 2

Rust is "pretty strongly typed" (and please, please don't call me out on how I measure how strongly typed a language is...). I mean this in the sense that Rust generally gives you the tools to let types "speak" for you and document your code, therefore it's idiomatic to use this feature to write readable code.

In other words, the question you're asking should be more "which type represents best what the function does to anyone who reads its signature?"

For Result<(), Workerror> you can see straight from the docs

Result is a type that represents either success (Ok) or failure (Err)

So, specialized for your case, it means your function returns nothing if it's successful (represented by Ok<()>) or WorkError if there's an error (Err<WorkError>). This is a very direct representation in code of the way you described the function in your question.

Compare this to Option<WorkError> or Option<()>

Type Option represents an optional value: every Option is either Some and contains a value or None, and does not

In your case Option<WorkError> would be saying to the reader "this function should return a WorkError but it may return nothing". You can document that the "return nothing" case means that the function was actually successful, but that's not very evident from types alone.

Option<()> says "this function can return nothing or have no meaningful return", which can be a reasonable thing to say if WorkError contains no other info (like an error type or an error message) and it's practically only a way to say "an error has occurred". In this case a simple bool carries the same information... Otherwise the Result lets you return some more info associated with the error.

Share:
15,571

Related videos on Youtube

Others
Author by

Others

Student, Programmer, Pedant

Updated on June 06, 2022

Comments

  • Others
    Others almost 2 years

    In Rust, I believe the idiomatic way to deal with recoverable errors is to use Result. For example this function clearly is idiomatic:

    fn do_work() -> Result<u64, WorkError> {...}
    

    Of course, there are also functions that have a single, obvious, failure state, and therefore use the Option type instead. An idiomatic example would be this:

    fn do_work() -> Option<u64>
    

    This all is straightforwardly addressed in the documentation. However, I'm confused about the case where a function can fail, but has no meaningful value when successful. Compare the following two functions:

    fn do_work() -> Option<WorkError>
    // vs
    fn do_work() -> Result<(), WorkError>
    

    I'm just not sure which one of these is more idiomatic, or is used more often in real world Rust code. My go-to resource for questions like this is the Rust book, but I don't think this is addressed in its "Error Handling" section. I haven't had much luck with any other Rust documentation either.

    Of course this seems pretty subjective, but I'm looking for authoritative sources that either state which form is idiomatic, or on why one form is superior (or inferior) to the other. (I'm also curious how the convention compares to other languages that heavily utilize "errors as values", like Go and Haskell.)

    • Simon Whitehead
      Simon Whitehead about 8 years
      I am of the Result<(), Error> side of things.. I usually alias these as my own types as well. I would be interested to hear what others say. I do this though because the try! macro still plays very nicely with it.
  • Others
    Others about 8 years
    I don't think I disagree, I do think Result<(), WorkError> is slightly nicer. However, I'm wondering if there are any "official" sources that support your answer in any way?
  • WiSaGaN
    WiSaGaN about 8 years
    @Others Since do_work is itself an conceptual example, I don't expect there will be any official convention for it. But you can get a sense of the idiom in standard library, for example: doc.rust-lang.org/nightly/std/io/…
  • huon
    huon about 8 years
    @Others the whole standard library uses Result<(), _> pervasively when an operation that may fail doesn't return anything useful (e.g. many functions of std::io and std::fs). It is the correct choice.
  • ArtemGr
    ArtemGr about 8 years
    @Others, using Result is officially endorsed at aturon.github.io/errors/signaling.html#obstructions (per RFC 236).
  • WiSaGaN
    WiSaGaN about 8 years
    @ArtemGr Note that the alternative discussed in that page would be Option<()> in OP question's context instead of Option<WorkError>. None in former means work is not done properly, while None in latter means work is done properly.
  • bluss
    bluss about 8 years
    @WiSaGaN Option does not assign a success/failure meaning to its variants, only present, not present.
  • WiSaGaN
    WiSaGaN about 8 years
    @bluss yeah, usually the difference between Result<Value, Error> and Option<Value> is obvious. Very rarely there is indeed some usage not that obvious. For example, to non-blockingly get an item from a queue, Option<Item> seems reasonable here, while it's not uncommon to have Result<Item, TryError>, which maps TryError to something like POSIX WOULDBLOCK.