Alternate/custom key types Any type that implements the Eq and Hash traits can be a key in HashMap. This includes: • bool (though not very useful since there are only two possible keys) • int, uint, and all variations thereof • String and &...
HashMap Where vectors store values by an integer index, HashMaps store values by key. HashMap keys can be booleans, integers, strings, or any other type that implements the Eq and Hash traits. More on this in the next section. Like vectors,...
panic! The panic! macro can be used to generate a panic and start unwinding its stack. While unwinding, the runtime will take care of freeing all the resources owned by the thread by calling the destructor of all its objects. Since we are d...
? Chaining results using match can get pretty untidy; luckily, the ? operator can be used to make things pretty again. ? is used at the end of an expression returning a Result, and is equivalent to a match expression, where the Err(err) bra...
Result We've seen that the Option enum can be used as a return value from functions that may fail, where None can be returned to indicate failure. However, sometimes it is important to express why an operation failed. To do this we have the...
Option Sometimes it's desirable to catch the failure of some parts of a program instead of calling panic!; this can be accomplished using the Option enum. The Option<T> enum has two variants: • None, to indicate failure or lack of value, an...
Strings The two most used string types in Rust are String and &str. A String is stored as a vector of bytes (Vec<u8>), but guaranteed to always be a valid UTF-8 sequence. String is heap allocated, growable and not null terminated. &str is a...
Vectors Vectors are re-sizable arrays. Like slices, their size is not known at compile time, but they can grow or shrink at any time. A vector is represented using 3 parameters: • pointer to the data • length • capacity The capacity indicat...
Box, stack and heap All values in Rust are stack allocated by default. Values can be boxed (allocated on the heap) by creating a Box<T>. A box is a smart pointer to a heap allocated value of type T. When a box goes out of scope, its destruc...
Std library types The std library provides many custom types which expands drastically on the primitives. Some of these include: • growable Strings like: "hello world" • growable vectors: [1, 2, 3] • optional types: Option<i32> • error hand...
Iterating over Results An Iter::map operation might fail, for example: fn main() { let strings = vec!["tofu", "93", "18"]; let numbers: Vec<_> = strings .into_iter() .map(|s| s.parse::<i32>()) .collect(); println!("Results: {:?}", numbers);...
Wrapping errors An alternative to boxing errors is to wrap them in your own error type. use std::error; use std::error::Error; use std::num::ParseIntError; use std::fmt; type Result<T> = std::result::Result<T, DoubleError>; #[derive(Debug)]...
Other uses of ? Notice in the previous example that our immediate reaction to calling parse is to map the error from a library error into a boxed error: .and_then(|s| s.parse::<i32>()) .map_err(|e| e.into()) Since this is a simple and commo...
Boxing errors A way to write simple code while preserving the original errors is to Box them. The drawback is that the underlying error type is only known at runtime and not statically determined. The stdlib helps in boxing our errors by ha...
Defining an error type Sometimes it simplifies the code to mask all of the different errors with a single type of error. We'll show this with a custom error. Rust allows us to define our own error types. In general, a "good" error type: • R...
Pulling Results out of Options The most basic way of handling mixed error types is to just embed them in each other. use std::num::ParseIntError; fn double_first(vec: Vec<&str>) -> Option<Result<i32, ParseIntError>> { vec.first().map(|first...
Multiple error types The previous examples have always been very convenient; Results interact with other Results and Options interact with other Options. Sometimes an Option needs to interact with a Result, or a Result<T, Error1> needs to i...
Introducing ? Sometimes we just want the simplicity of unwrap without the possibility of a panic. Until now, unwrap has forced us to nest deeper and deeper when what we really wanted was to get the variable out. This is exactly the purpose...
Early returns In the previous example, we explicitly handled the errors using combinators. Another way to deal with this case analysis is to use a combination of match statements and early returns. That is, we can simply stop executing the...
aliases for Result How about when we want to reuse a specific Result type many times? Recall that Rust allows us to create aliases. Conveniently, we can define one for the specific Result in question. At a module level, creating aliases can...