To illustrate, imagine we had some code to read some data from a file: Note: this code could be made simpler with a single call to std::FS::read_to_string, but we're writing it all out manually here to have an example with multiple errors.
This code has two paths that can fail, opening the file and reading the data from it. Doing so involves match ING on the result of the I/O operations.
If it was an Err, it returns from the function you're currently in. Character to indicate that here we are handling errors in the standard way, by passing them up the call stack.
Has proved to be extremely useful, and is used often in idiomatic Rust. This sort of evolution is one of the great advantages of a powerful macro system: speculative extensions to the language syntax can be prototyped and iterated on without modifying the language itself, and in return, macros that turn out to be especially useful can indicate missing language features.
This brings undue attention to the trivial error propagation, obscuring the main code path, in this example the calls to foo, bar and BAZ. This sort of method chaining with error handling occurs in situations like the builder pattern.
Finally, the dedicated syntax will make it easier in the future to produce nicer error messages tailored specifically to ? , whereas it is difficult to produce nice errors for macro-expanded code generally.
Ever seen a line of Rust code with a rising inflection, with an inquisitive tone? It’s a piece of syntax that replaces a very common case of error handling.
The following piece of code deals with 2 operations that can fail. It tries to open a file and read the username inside.
If any of the operations fail, it will stop execution of the function and return information about that failure. The read_username_from_file function returns a Result
Inside read_username_from_file there is a call to File::open, which returns a Result type. It can return an Err with an error inside (an Io::Error).
To deal with that possibility, the code calls a match on the result of the File::open function. To put it differently: it either unwraps the Ok, or it quits the function and returns the Err.
Later in the read_username_from_file function another operation that returns a Result is called. If the returned value is an Ok, this signals a successful completion of the function.
On Ok, it unwraps the Ok and evaluates to the value inside on Err, it returns from the function with that Err (that holds an Io::Error in this case) The possible Err from File::open is returned from the function.
If there is no error, f will hold the file handle the Ok contained and execution of the function continues. If there is no error, the method finished successfully and execution of the function continues.
If the code reaches a point after that line, it means the result was Ok. The last expression in this rewritten function is an Ok with a String inside: Ok(s).
There is a slight difference between the 2 pieces of code. Possibly exits the function with an Err(error), that error must be compatible with the E in the Result the function returns.
It has panics, but their functionality is limited (they cannot carry structured information) and their use for error-handling is discouraged (they are meant for unrecoverable errors). In Rust, error handling uses Result.
When writing the code you cannot accidentally forget to deal with the error, when reading the code you can immediately see that there is a potential for error right here. In short: it unpacks the Result if OK and returns the error if not.
Rust's error handling revolves around returning Result
In Rust 2018 you can instead let your # s and main functions return a Result : Getting Result<.> to work in the context of main and # s is not magic.
It is all backed up by a Termination trait which all valid return types of main and testing functions must implement. When setting up the entry point for your application, the compiler will use this trait and call.report() on the Result of the main function you have written.
28 Jan 2016For people who are not familiar with Haskell or Scala, Rust ’s Option and Result types might feel a bit cumbersome and verbose to work with. To make it easier and less verbose to use them the RFC PR #243: Trait-based exception handling has been proposed.
Macro, making it easier to write code chaining expressions which can fail: The Result type will automatically re-wrap the return value of the block if there is no catch block, so that the whole expression assumes a Result
The first line inside the do -block is a so called monodic bind: it will bind the value contained inside the type on the right of to the identifier on the left for the rest of the block. The result of the rest of the block will be merged with the context from the value on the right of .
In the case of Result and Option this is very simple and would just not evaluate the rest of the block if the right-hand side is of an error-variant. The two expressions are compatible since they both return a value of type Result
Though in the code examples above would be more suitable to just move the expression assigned to s into the call to write_all. It would also be desirable to allow the use of normal let -binds inside do -blocks to allow the declarations of variables without having to use monodic bind.
Note that we cannot use logging_on directly as the condition of the if -expression and that the if -expression needs to return a Result from both branches. Utility functions can easily alleviate some of this, but Higher Minded Types are required to make many of them generic enough.
The Result and Option monads only carry state in the type, e.g. Is M
Monodic bind on the other hand has the signature M
The closure denoted FN* is one of the three closure types Nonce, Input and FN since different types of bind-implementations have different requirements (e.g. Result would use Nonce since it is just run once, whereas an iterator would need Input since the closure would be executed once for each item).
By adding a = M constraint to R we can allow the wrapping method to investigate and update the state of any returned M and actually provide a proper monodic bind for the type. Though the proposal is lacking one very important piece which is needed to make it at all possible, and that is how the state should be carried over from the original M
A monad carrying two values) but without the needed restrictions on the types or any way of carrying state from the left-hand side to the return value. The try -blocks or do -notation is another matter, if implemented properly this would be a nice composable way of dealing with computations in a context.