Going wrong
Other than classes, Rust comes without another well-known companion: null. In the absence of pointers and with a very different memory management model, there is no typical null pointer/reference.
Instead, the language works with Option and Result types that let developers model success or failure. In fact, there is no exception system either, so any failed execution of a function should be indicated in the return type. Only in rare cases when immediate termination is required does the language provide a macro for panicking: panic!().
Option<T> and Result<T, E> both encapsulate one (Option<T>) or two (Result<T, E>) values that can be returned to communicate an error or whether something was found or not. For example, a find() function could return Option<T>, whereas something like read_file() would typically have a Result<T, E> return type to communicate the content or errors:
fn find(needle: u16, haystack: Vec<u16>) -> Option<usize> {
// find the needle in the haystack
}
fn read_file(path: &str) -> Result<String, io::Error> {
// open the path as a file and read it
}
Handling those return values is often done with match or if let clauses in order to handle the cases of success or failure:
match find(2, vec![1,3,4,5]) {
Some(_) => println!("Found!"),
None => println!("Not found :(")
}
// another way
if let Some(result) = find(2, vec![1,2,3,4]) {
println!("Found!")
}
// similarly for results!
match read_file("/tmp/not/a/file") {
Ok(content) => println!(content),
Err(error) => println!("Oh no!")
}
This is due to Option<T> and Result<T, E> both being enumerations that have generic type parameters; they can assume any type in their variants. Matching on their variants provides access to their inner values and types to allow a branch of the code to be executed and handle the case accordingly. Not only does this eliminate the need for constructs such as try/catch with multiple—sometimes cast—exception arms, it makes failure part of the normal workflow that needs to be taken care of.