The Rust Programming Language
Level up not just your knowledge of Rust, but also your reach and confidence as a programmer in general.
Source: https://doc.rust-lang.org/1.30.0/book/2018-edition/
Quotes
Common Programming Concepts
let tup: (i32, f64, u8) = (500, 6.4, 1);A tuple is a general way of grouping together some number of other values with a variety of types into one compound type.
Understanding Ownership
let s = String::from("hello world"); let hello = &s[0..5];A string slice is a reference to part of a String. The type that signifies “string slice” is written as &str.
let s = "Hello, world!";String literals are slices. Other types of collections, like arrays, may also be sliced.
Using Structs to Structure Related Data
struct User { username: String, email: String };A struct, or structure, is a custom data type that lets you name and package together multiple related values that make up a meaningful group. A field is the name and type of a piece of data in the struct.
let user1 = User { email: String::from("someone@example.com"), username: String::from("someusername123") };We create an instance by stating the name of the struct and then add curly brackets containingkey: valuepairs.
let user2 = User { ..user1 };The syntax..specifies that the remaining fields not explicitly set should have the same value as the fields in the given instance.
struct Color(i32, i32, i32);Tuple structs have the added meaning the struct name provides but don’t have names associated with their fields.
println!("rect1 is {:?}", rect1);Putting the specifier:?inside the curly brackets tellsprintln!we want to use an output format calledDebug.
impl Rectangle { fn area(&self) -> u32 { self.width * self.height } }Methods are similar to functions, but they're defined within the context of a struct (or an enum or a trait object) and their first parameter is alwaysself, which represents the instance of the struct.
Associated Functions are functions defined within impl blocks that do not take self as a parameter. They let you namespace functionality (::) that is particular to your struct without having an instance available.
LikefrominString::from, associated functions are often used for constructors that will return a new instance of the struct.
Enums and Pattern Matching
enum IpAddr { V4(u8,u8,u8,u8), V6(String), }Enums allow you to define a type by enumerating its possible values. The possible kinds of an enum are called variants.
enum Option<T> { Some(T), None, }Rust does not have nulls, but it does have an enum that can encode the concept of a value being present or absent.
let absent_number: Option<i32> = None;The<T>generic type parameter represents any one piece of data of any type.
The compiler won't let us use anOption<T>value as if it were definitely a valid value. In order to have a value that can possibly be null, you must explicitly opt in by making the type of that valueOption<T>and handle the possibility of it being null.
matchis a powerful control flow operator similar to anif, but it can match any type.
match coin { Coin::Penny => 1, Coin::Nickel => 5, }An arm of amatchexpression is a pattern and the code to execute.
The_pattern will match any value.
if let Some(3) = some_u8_value { println!("three"); }if letmay be used instead of match to run code that should only execute if one specific pattern matches. Anelseinif letis like_inmatch.
Using Modules to Reuse and Organize Code
Themodkeyword declares a new module. Theusekeyword brings modules into scope.mod client;means "the contents of file client.rs".
If an item is private, it can be accessed only by its immediate parent module and any of the parent's child modules
use a::series::of; fn main() { of::nested_modules(); }Rather than using the fulla::series::ofpath we can now useof. The use keyword brings only what we’ve specified into scope: it does not bring children of modules into scope.
use TrafficLight::{Red, Yellow};Becauseenumsalso form a sort of namespace like modules, we can bring an enum's variants into scope as well.
use foo::{ bar::{self, Foo}, baz::{*, quux::Bar}, };Theusedeclaration supports nesting to help you group imports in the same declaration and avoid repeating the base modules' name.
use TrafficLight::*;To bring all the items in a namespace into scope at once, we can use the*glob operator. Use this sparingly. It might pull in more items than you expect, causing naming conflicts.
Foruse, paths are relative to the crate root by default. Otherwise paths are always relative to the current module. Usesuper::to access the parent module and thus get from the current module to sibling modules.
Common Collections
Collections, like vector, string and hash map, can contain multiple values. Their data lives in the heap and does not need to be known at compile time.
let v = vec![1, 2, 3]; let mut vv = Vec::new(); v.push(5);When values are provided, Rust will infer the types the vector holds.
let out_of_range = 100; let does_not_exist = &v[out_of_range]; let does_not_exist = v.get(out_of_range);Using indexing syntax will have your program crash, while usingget()allows you to handle the resultingNone.
for i in &v { println!("{}", i); }Iterating over immutable references to elements of a vector versus iterating over mutable ones:for i in &mut matuble_v { *i += 50; }The dereference operator*gets us the value ini.
et mut s = String::from("lo"); s.push('l');The push method takes a single character as a parameter and adds it to the String.posh_strpushes a slice.
A String is a wrapper over aVec<u8>.
for b in "नमस्ते".bytes() { println!("{}", b); }Thebytesmethod returns each raw byte, while.chars()separates out and returns six values of typechar.
use std::collections::HashMap; [...] scores.insert(String::from("Blue"), 10);The typeHashMap<K, V>stores a mapping of keys of typeKto values of typeV. You can create an empty hash map with new and add elements with insert.
let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();Given two separate vectors, we can usezipcreate a vector of tuples, then, withcollectturn that vector of tuples into a hash map.
let score = scores.get(&team_name);We can get a value out of the hash map by providing its key to thegetmethod.
scores.entry(String::from("Blue")).or_insert(50);The return value of theentryfunction is an enum calledEntrythat represents a value that might or might not exist. This only inserts a value if the key has none yet.
Error Handling
let f = File::open("hello.txt").unwrap();If the Result value is theOkvariant,unwrapwill return the value inside theOk. If the Result is theErrvariant,unwrapwill call thepanic!macro for us.
let f = File::open("hello.txt").expect("Failed to open hello.txt");Usingexpectinstead ofunwrapand providing good error messages can convey your intent and make tracking down the source of a panic easier.
File::open("hello.txt")?.read_to_string(&mut s)?;Values taken by?go through thefromfunction, which is used to convert errors from one type into another.
As long as each error type implements thefromfunction to define how to convert itself to the returned error type,?takes care of converting errors from one type to another automatically.
Functions often have contracts their behavior is only guaranteed if the inputs meet particular requirements. Panicking when the contract is violated makes sense because a contract violation always indicates a caller-side bug.
continuestarts the next iteration of the current loop.
pub fn value(&self) -> i32 { self.value }Thevaluegetter method borrowsself, doesn't have any other parameters and returns ani32.
A getter method on astructsallows thevaluefield to remain readable while code using the encapsulatingstructis denied changingvaluedirectly, which would evade the checks inGuess:new.
Generic Types, Traits, and Lifetimes (TODO)
Writing Automated Tests
#[derive(PartialEq, Debug)]For structs and enums that you define, you’ll need to implement PartialEq and Debug in order to use them in the likes of assert_eq!.
assert!( result.contains("Carol"), "Failed. Value was `{}`", result);Tests can contain adding custom failure Messages.
#[should_panic(expected = "must be less than 101")]Theshould_panicattribute makes a test pass if the code inside the function panics.expectedmakes sure that the failure message contains the provided text.
fn it_works() -> Result<(), String> { ... }Tests can be asserted usingResult<T, E>, returningOk(())for the success case, and an Err with a String inside for the failure case.
We can run all the tests in a module by filtering on the module's name.
The#[cfg(test)]annotation on the tests module tells Rust to compile and run the test code only when you runcargo test, not when you runcargo build.
mod common; [...] common::setup()Files in subdirectories of the tests directory don't get compiled as separate crates or have sections in the test output. By putting helper functions into tests/common/mod.rs, we avoid them being considered and executed as tests.
Rust projects that provide a binary have a straightforwardsrc/main.rsfile that calls logic that lives in thesrc/lib.rs file. This allows the functionality in lib.rs to be tested via integration tests.