Rust Cheatsheet
Simple program
main.rs
fn main() {
println!("test");
}
Naming
Use underscores to separate words; hello_world.rs
.
Cargo project
Cargo is rusts native build and dependency management tool and defines a project folder structure
- src Source and unit test files
- target
- tests Integration tests
- Cargo.toml
- Cargo.lock Generated file; concrete dependencies (e.g. concrete git revision) Should be versioned for executable projects (not cargo libraries)
$ cargo new projectname --bin
$ cargo build
$ cargo build --release
$ cargo run
$ cargo build
$ cargo doc --open
Functions and Macros
println!("test"); // Macro call
println("test"); // Function call
String::new()
Associated function (in other langauges called static method)
use std::io;
Variables - Mutability, Shadowing
// Variables are immutable by default
let x = 2;
// Compiler error:
//x = 3;
let mut y = 3;
y = 5;
let y = y + 3;
println!("Shadowed y: {}", y)
// Constants - always immutable and type annotated
const MAX_POINTS: u32 = 100_000;
println!("Constant MAX_POINTS: {}", MAX_POINTS);
Types
Scalar
Integer Types
Length | Signed | Unsigned | Range |
---|---|---|---|
8-bit | i8 | u8 | 2^8 = 256 = 0xFF = (s) -128;127 |
16-bit | i16 | u16 | 2^16 = 65,536 = 0xFFFF = (s) |
32-bit | i32 | u32 | 2^32 = 4,294,967,296 = 0xFFFF_FFFF |
64-bit | i64 | u64 | 2^64 = 18,446,744,073,709,551,616 = 0xFFFF_FFFF_FFFF_FFFF |
arch | isize | usize |
Integer Literals
Number literals | Example |
---|---|
Decimal | 98_222 |
Hex | 0xff |
Octal | 0o77 |
Binary | 0b1111_0000 |
Byte (u8 only) | b’A' |
Floating-point Types
let x: f64 = 2.0;
let x: f32 = 2.0;
// Implicitly f64
let x = 2.0;
Numeric Operations
// addition
let sum = 5 + 10;
// subtraction
let difference = 95.5 - 4.3;
// multiplication
let product = 4 * 30;
// division
let quotient = 56.7 / 32.2;
// remainder
let remainder = 43 % 5;
Boolean
let t = true;
let f: bool = false;
Character
let c = 'z';
let z: char = 'ℤ';
let heart_eyed_cat = '😻';
Compound Types
Compound types can group multiple values of other types into one type. Rust has two primitive compound types: tuples and arrays.
Tuple
A tuple is a general way of grouping together some number of other values with a variety of types into one compound type.
let tup_explicit: (i32, f64, u8) = (500, 6.4, 1);
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("The value of y is: {}", y);
Array
Unlike a tuple, every element of an array must have the same type.
Arrays in Rust are different than arrays in some other languages because arrays in Rust have a fixed length: once declared, they cannot grow or shrink in size.
Arrays are useful when you want your data allocated on the stack rather than the heap.
let a = [1, 2, 3, 4, 5];
let first = a[0];
let second = a[1];
// Accessing invalid index produces a panic
//let element = a[index];
Functions
fn main() {
another_function(5, 6);
}
fn another_function(x: i32, y: i32) {
println!("The value of x is: {}", x);
println!("The value of y is: {}", y);
}
Statements and Expressions
// Statement
let x = 5;
// ^ Expression "5"
let y = {
let x = 3;
x + 1
};
// Expression {} with last line without a semicolon
println!("The value of y is: {}", y);
Return Values
fn five() -> i32 {
5
}
fn plus_one(x: i32) -> i32 {
x + 1
}
Comments
// Line comments
Control Flow
if
if 2 < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
if is an expression
let condition = true;
let number = if condition {
5
} else {
6
};
println!("The value of number is: {}", number);
Loops
loop {
println!("again!");
}
while (true) {
}
let a = [10, 20, 30, 40, 50];
for element in a.iter() {
println!("the value is: {}", element);
}
for number in (1..4).rev() {
println!("{}!", number);
}
println!("LIFTOFF!!!");
Ownership
Stack: push and pop; last in, first out; fixed size at compile time
Heap: memory is allocated and freed at runtime
- Each value in Rust has a variable that’s called its owner.
- There can only be one owner at a time.
- When the owner goes out of scope, the value will be dropped.
{ // s is not valid here, it’s not yet declared
let s = "hello"; // s is valid from this point forward
// do stuff with s
} // this scope is now over, and s is no longer valid
// Immutable string literal; stack
let a = "A string literal";
// Heap allocated
let b = String::from("hello");
b.push_str(", world!");
Move
let s1 = String::from("hello");
let s2 = s1;
// Invalid; Compiler error
//println!("{}", s1);
Clone
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
Stack only data Copy
Stack data is always copied.
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y);
Traits: Copy vs Drop
A type can only have either the Copy or the Drop trait.
Copy:
- All the integer types, like u32.
- The boolean type, bool, with values true and false.
- All the floating point types, like f64.
- Tuples, but only if they contain types that are also Copy. (i32, i32) is Copy, but (i32, String) is not.
Functions
let s = String::from("hello");
takes_ownership(s);
let x = 5;
makes_copy(x);
Return Values
References
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
}
// Borrowing per reference (immutable by default)
fn calculate_length(s: &String) -> usize {
s.len()
}
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
Only one mutable reference can exist in a particular scope. Prevents data races. (Also can not create a mutable reference in the scope of an immutable reference.)
let mut s = String::from("hello");
let r1 = &mut s;
// Will fail with compiler error
//let r2 = &mut s;
Dangling References
Prevented by the compiler.
Rules of References
- At any given time, you can have either but not both of:
-
- One mutable reference.
-
- Any number of immutable references.
- References must always be valid.
Slices
Determining and holding index values could become invalid on content changes. Slices keep references to owned values. (Compiler guaranteed validity.)
let s = String::from("hello world");
let hello = &s[0..5];
// Equal, not specifying start index 0
let hello = &s[..2];
let world = &s[6..11];
let world = &s[6..len];
let world = &s[6..];
// Entire string
let slice = &s[0..len];
let slice = &s[..];
// Will produce a compiler error.
//s.clear();
Inclusive begin, exclusive end index.
String literals are slices
let s: &str = "Hello, world!";
Other Slices
let a = [1, 2, 3, 4, 5];
let slice: &[i32] = &a[1..3];
Structs
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
println!("{}", user1.email);
user2.email = String::from("someone-else@example.com");
field init shorthand
If you have variables with the same names as struct fields, you can use field init shorthand.
fn build_user(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}
New Struct with Update
let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
..user1
};
Tuple Structs
- Tuple: unnamed type and unnamed values (by order)
- Struct: Named type and named fields
- Tuple struct: Named type, unnamed fields (type checkability)
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
Unit-Like Structs Without Any Fields
The unit type: ()
Ownership of Struct data
For a struct to be able to hold a reference to data it does not own, lifetimes are necessary. [later]
Debug trait
#[derive(Debug)]
struct Rectangle {
length: u32,
width: u32,
}
// Fails without the Display trait
// println!("rect1 is {}", rect1);
// Fails without the Debug trait;
// can be automatically added via #[derive(Debug)]
println!("rect1 is {:?}", rect1);
// Expant debug format
println!("rect1 is {:#?}", rect1);
Methods
struct Rectangle {
length: u32,
width: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.length * self.width
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.length > other.length && self.width > other.width
}
// Associated function
fn square(size: u32) -> Rectangle {
Rectangle { length: size, width: size }
}
}
fn main() {
let rect1 = Rectangle { length: 50, width: 30 };
println!("{}", rect1.area());
let rect1 = Rectangle { length: 50, width: 30 };
let rect2 = Rectangle { length: 40, width: 10 };
let rect3 = Rectangle { length: 45, width: 60 };
println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
let sq = Rectangle::square(3);
}
Enums
enum IpAddrKind {
V4,
V6,
}
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
fn route(ip_type: IpAddrKind) { }
route(IpAddrKind::V4);
route(IpAddrKind::V6);
V4 and V6 are “variants” of the enum IpAddrKind
Associated values:
enum IpAddr {
V4(String),
V6(String),
}
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
struct Ipv4Addr {
}
struct Ipv6Addr {
}
enum IpAddr {
V4(Ipv4Addr),
V6(Ipv6Addr),
}
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
impl Message {
fn call(&self) {
// method body would be defined here
}
}
let m = Message::Write(String::from("hello"));
m.call();
Option enum
Option
Included in the prelude.
Some() and None can be used without qualification.
enum Option<T> {
Some(T),
None,
}
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
match
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> i32 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
fn value_in_cents_commented(coin: Coin) -> i32 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
},
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
#[derive(Debug)] // So we can inspect the state in a minute
enum UsState {
Alabama,
Alaska,
// ... etc
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn value_in_cents(coin: Coin) -> i32 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
},
}
}
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
// i binds to the value contained in Some
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
Not catching all cases in a match is a compiler error.
The _ placeholder
We only care about the values 1, 3, 5, 7 of an u8 integer:
let some_u8_value = 0u8;
match some_u8_value {
1 => println!("one"),
3 => println!("three"),
5 => println!("five"),
7 => println!("seven"),
_ => (),
}
if let
A shorthand for a match statement with only one arm.
if let Some(3) = some_u8_value {
println!("three");
}
With else
case:
let mut count_no_quarter = 0;
if let Coin::Quarter(state) = coin {
println!("State quarter from {:?}!", state);
} else {
count_no_quarter += 1;
}
Modules
Units for code structuring and reuse.
- The mod keyword declares a new module. Code within the module appears either immediately following this declaration within curly braces or in another file.
- By default, functions, types, constants, and modules are private. The pub keyword makes an item public and therefore visible outside its namespace.
- The use keyword brings modules, or the definitions inside modules, into scope so it’s easier to refer to them.
mod network {
// Use with network::connect
fn connect() {
}
}
mod client {
// Use with client::connect
fn connect() {
}
}
mod network {
fn connect() {
}
mod client {
fn connect() {
}
}
}
├── src
│ ├── client.rs
│ ├── lib.rs
│ └── network
│ ├── mod.rs
│ └── server.rs
mod outermost {
pub fn middle_function() {}
fn middle_secret_function() {}
mod inside {
pub fn inner_function() {}
fn secret_function() {}
}
}
fn try_me() {
outermost::middle_function();
// Fails
outermost::middle_secret_function();
// Fails
outermost::inside::inner_function();
// Fails
outermost::inside::secret_function();
}
use nested module
pub mod a {
pub mod series {
pub mod of {
pub fn nested_modules() {}
}
}
}
use a::series::of;
fn main() {
of::nested_modules();
}
use function
pub mod a {
pub mod series {
pub mod of {
pub fn nested_modules() {}
}
}
}
use a::series::of::nested_modules;
fn main() {
nested_modules();
}
use multiple enum values in one statement
enum TrafficLight {
Red,
Yellow,
Green,
}
use TrafficLight::{Red, Yellow};
fn main() {
let red = Red;
let yellow = Yellow;
let green = TrafficLight::Green;
}
*
glob
enum TrafficLight {
Red,
Yellow,
Green,
}
use TrafficLight::*;
fn main() {
let red = Red;
let yellow = Yellow;
let green = Green;
}
Collections
Vectors Vec<T>
let v: Vec>i32< = Vec::new();
let v = vec![1, 2, 3];
let mut v = Vec::new();
v.push(5);
v.push(6);
v.push(7);
v.push(8);
Dropping a Vector Drops its Elements (dropped when going out of scope).
let v = vec![1, 2, 3, 4, 5];
// Returns the element (or panics if not existant)
let third: &i32 = &v[2];
// Returns an Option<T>
let third: Option<&i32> = v.get(2);
<enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
Strings
The Display trait implements to_string().
let a = "initial content".to_string();
let b = String::from("initial content");
let mut s = String::from("foo");
s.push_str("bar");
s.push('l');
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
// Note that s1 has been moved here and can no longer be used
// Operator + uses add method,
// compiler makes use of deref coercion
// to coerce s2 String reference into a string slice &s2[..]
let s3 = s1 + &s2;
format! macro
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{}-{}-{}", s1, s2, s3);
Indexing, Unicode, byte, scalar, grapheme cluster
Hindi word “नमस्ते” written in the Devanagari script,
it is ultimately stored as a Vec of u8 values that looks like this:
[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164, 224, 165, 135]
Unicode scalar values (rusts char type): ['न', 'म', 'स', '्', 'त', 'े']
but the fourth and sixth are not letters, they’re diacritics that don’t make sense on their own
grapheme clusters (“letters”): ["न", "म", "स्", "ते"]
=> Not possible to index a value from a String.
// In this particular string every character is two bytes.
let hello = "Здравствуйте";
let s = &hello[0..4];
// Will panic at runtime for invalid index.
&hello[0..1]
for c in "नमस्ते".chars() {
println!("{}", c);
}
for b in "नमस्ते".bytes() {
println!("{}", b);
}
Working with grapheme clusters on Strings is not covered by the standard library.
Refer to external crates.
HashMap
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
For Copy trait values are copied, for owned values like String, they are moved.
collect
use std::collections::HashMap;
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
// zip creates a vector of tuples (first, second)
let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();
Get
let team_name = String::from("Blue");
let score = scores.get(&team_name);
for (key, value) in &scores {
println!("{}: {}", key, value);
}
Updating
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
// Overwrite
scores.insert(String::from("Blue"), 25);
println!("{:?}", scores);
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50);
println!("{:?}", scores);
use std::collections::HashMap;
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0);
// Dereference value and add 1 to it
*count += 1;
}
println!("{:?}", map);
Error Handling
Cargo.toml configuration to abort on panic (without stack unwinding):
[profile.release]
panic = 'abort'
enum Result<T, E> {
Ok(T),
Err(E),
}
use std::fs::File;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => {
panic!("There was a problem opening the file: {:?}", error)
},
};
}
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
// this if expression is a "match guard"
// ref so that the error is not moved into the guard condition
Err(ref error) if error.kind() == ErrorKind::NotFound => {
match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => {
panic!(
"Tried to create file but there was a problem: {:?}",
e
)
},
}
},
Err(error) => {
panic!(
"There was a problem opening the file: {:?}",
error
)
},
};
}
In short, in the context of a pattern, & matches a reference and gives us its value, but ref matches a value and gives us a reference to it.
unwrap()
unwraps the value from Result (Ok) or panics on error (Err variant).
let f = std::fs::File::open("hello.txt").expect("Failed to open hello.txt");
expect()
unwraps with a panic error text for the Err variant.
Propagating the error: return Err(e)
use std::io;
use std::io::Read;
use std::fs::File;
fn read_username_from_file() -> Result<String, io::Error> {
let f = File::open("hello.txt");
let mut f = match f {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut s = String::new();
match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(e) => Err(e),
}
}
Shorthand: ?
use std::io;
use std::io::Read;
use std::fs::File;
fn read_username_from_file() -> Result<String, io::Error> {
// Return Err early or the value on Ok
let mut f = File::open("hello.txt")?;
let mut s = String::new();
// Return Err early or the value on Ok
f.read_to_string(&mut s)?;
Ok(s)
}
Even shorter (chaining after ?
):
fn read_username_from_file() -> Result<String, io::Error> {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
?
can only be used in functions that return a Result
When prototyping unwrap and expect are useful, until you polish it with error handling.
If a method call fails in a test, we’d want the whole test to fail, even if that method isn’t the functionality under test. Because panic! is how a test gets marked as a failure, calling unwrap or expect is exactly what makes sense to do.
E.g. if we can verify it never Errs:
let home = "127.0.0.1".parse::<IpAddr>().unwrap();
To panic makes sense if the program is in a bad state:
The bad state is not something that’s expected to happen occasionally
Your code after this point needs to rely on not being in this bad state
There’s not a good way to encode this information in the types you use
Panic when the contract is violated.
Generics, Traits, Lifetimes
fn largest<T>(list: &[T]) -> T {
struct Point<T> {
x: T,
y: T,
}
let wont_work = Point { x: 5, y: 4.0 };
enum Option<T> {
Some(T),
None,
}
enum Result<T, E> {
Ok(T),
Err(E),
}
Generic methods
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
fn main() {
let p = Point { x: 5, y: 10 };
println!("p.x = {}", p.x());
}
struct Point<T, U> {
x: T,
y: U,
}
impl<T, U> Point<T, U> {
fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
Point {
x: self.x,
y: other.y,
}
}
}
Traits
Traits abstract over behavior that types can have in common.
pub trait Summarizable {
fn summary(&self) -> String;
}
Implementation of a trait:
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summarizable for NewsArticle {
fn summary(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summarizable for Tweet {
fn summary(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
Orphan rule: we may implement a trait on a type as long as either the trait or the type are local to our crate.
Default implementation
pub trait Summarizable {
fn summary(&self) -> String {
String::from("(Read more...)")
}
}
// Use default trait method implementation
impl Summarizable for NewsArticle {}
Trait methods can call other trait methods.
Trait bounds
pub fn notify<T: Summarizable>(item: T) {
println!("Breaking news! {}", item.summary());
}
Multiple traits with +
fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {
Or with a where clause:
fn some_function<T, U>(t: T, u: U) -> i32
where T: Display + Clone,
U: Clone + Debug
{
Lifetimes
&i32 // a reference
&'a i32 // a reference with an explicit lifetime
&'a mut i32 // a mutable reference with an explicit lifetime
longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
The lifetime of the returned reference depends on which reference (x or y) is chosen.
This is represented by the 'a
lifetime.
The returned reference is valid as long as both the referenced x and y are valid.
For some lifetime ‘a, the function will get two parameters, both of which are string slices that live at least as long as the lifetime ‘a. The function will return a string slice that also will last at least as long as the lifetime ‘a.
lifetime syntax is about connecting the lifetimes of various arguments and return values of functions.
In a struct (struct with reference)
struct ImportantExcerpt<'a> {
part: &'a str,
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.')
.next()
.expect("Could not find a '.'");
let i = ImportantExcerpt { part: first_sentence };
}
Lifetime Elision
// Simple, lifetime pattern recognized by compiler
fn first_word(s: &str) -> &str {
// Elaborative
fn first_word<'a>(s: &'a str) -> &'a str {
Lifetimes on function or method parameters are called input lifetimes, and lifetimes on return values are called output lifetimes.
- Each parameter that is a reference gets its own lifetime parameter. In other words, a function with one parameter gets one lifetime parameter: fn foo<‘a>(x: &‘a i32), a function with two arguments gets two separate lifetime parameters: fn foo<‘a, ‘b>(x: &‘a i32, y: &‘b i32), and so on.
- If there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters: fn foo<‘a>(x: &‘a i32) -> &‘a i32.
- If there are multiple input lifetime parameters, but one of them is &self or &mut self because this is a method, then the lifetime of self is assigned to all output lifetime parameters. This makes writing methods much nicer.
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
}
impl<'a> ImportantExcerpt<'a> {
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention please: {}", announcement);
self.part
}
}
‘static lifetime
entire duration of the program
let s: &'static str = "I have a static lifetime.";
Combination
use std::fmt::Display;
fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str
where T: Display
{
println!("Announcement! {}", ann);
if x.len() > y.len() {
x
} else {
y
}
}
Tests
test attribute annotation: #[test]
mod with #[cfg(test)]
assert!(!smaller.can_hold(&larger));
assert_eq!(4, add_two(2));
assert_ne!(4, add_two(2));
#[should_panic]
#[should_panic(expected = "Guess value must be less than or equal to 100")]
#[ignore]
assert!(
result.contains("Carol"),
"Greeting did not contain name, value was `{}`", result
);
When the assertions fail, these macros print their arguments using debug formatting, which means the values being compared must implement the PartialEq and Debug traits.
#[derive(PartialEq, Debug)]
$ cargo test -- --test-threads=1
$ cargo test -- --nocapture
// Run only test one_hundred
$ cargo test one_hundred
// Run all tests with add in their name or module
$ cargo test add
$ cargo test -- --ignored
// per file
$ cargo test --test integration_test
tests/common/mod.rs
for common logic for integration tests.
Binary projects will have little logic in src/main.rs, mostly calls stuff in src/lib.rs
, which is thus testable.
Closures
let expensive_closure = |num| {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
num
};
expensive_closure(intensity)
Optional type annotations:
let expensive_closure = |num: i32| -> i32 {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
num
};
fn add_one_v1 (x: i32) -> i32 { x + 1 }
let add_one_v2 = |x: i32| -> i32 { x + 1 };
let add_one_v3 = |x| { x + 1 };
let add_one_v4 = |x| x + 1 ;
let config = Config::new(&args).unwrap_or_else(|err| {
println!("Problem parsing arguments: {}", err);
process::exit(1);
});
Traits Fn, FnMut, FnOnce
struct Cacher<T>
where T: Fn(i32) -> i32
{
calculation: T,
value: Option<i32>,
}
impl<T> Cacher<T>
where T: Fn(i32) -> i32
{
fn new(calculation: T) -> Cacher<T> {
Cacher {
calculation,
value: None,
}
}
fn value(&mut self, arg: i32) -> i32 {
match self.value {
Some(v) => v,
None => {
//FIXME: Assumes arg will always be the same
let v = (self.calculation)(arg);
self.value = Some(v);
v
},
}
}
}
Captures Environment
fn main() {
let x = 4;
let equal_to_x = |z| z == x;
let y = 4;
assert!(equal_to_x(y));
}
Closures can capture values from their environment in three ways, which directly map to the three ways a function can take a parameter: taking ownership, borrowing immutably, and borrowing mutably. These ways of capturing values are encoded in the three Fn traits as follows:
- FnOnce takes ownership of the variables it captures from the environment and moves those variables into the closure when the closure is defined. Therefore, a FnOnce closure cannot be called more than once in the same context.
- Fn borrows values from the environment immutably.
- FnMut can change the environment since it mutably borrows values.
If we want to force the closure to take ownership of the values it uses in the environment, we can use the move keyword before the parameter list. This is mostly useful when passing a closure to a new thread in order to move the data to be owned by the new thread.
let x = vec![1, 2, 3];
let equal_to_x = move |z| z == x;