What is the idiomatic way to return an error from a function with no result if successful?
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.
Related videos on Youtube
Comments
-
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 about 8 yearsI 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 thetry!
macro still plays very nicely with it.
-
-
Others about 8 yearsI 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 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 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 ofstd::io
andstd::fs
). It is the correct choice. -
ArtemGr about 8 years@Others, using
Result
is officially endorsed at aturon.github.io/errors/signaling.html#obstructions (per RFC 236). -
WiSaGaN about 8 years@ArtemGr Note that the alternative discussed in that page would be
Option<()>
in OP question's context instead ofOption<WorkError>
.None
in former means work is not done properly, whileNone
in latter means work is done properly. -
bluss about 8 years@WiSaGaN Option does not assign a success/failure meaning to its variants, only present, not present.
-
WiSaGaN about 8 years@bluss yeah, usually the difference between
Result<Value, Error>
andOption<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 haveResult<Item, TryError>
, which mapsTryError
to something like POSIXWOULDBLOCK
.