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()functioncould returnOption<T>, whereas something likeread_file()would typically have aResult<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.