Enumeration
- Data that can be one of multiple different possibilities
- Each possibiliy is called a “variant”
- Provides information about your program to the compiler
- More robust programs
Recap
- Enum can only be one vatiant at a time
- More robust programs when paired with match
- Make program code easier to read
Demo: enum
enum Direction {
Up,
Down,
Left,
Right,
}
fn main() {
let go = Direction::Right;
match go {
Direction::Up => println!("go up"),
Direction::Down => println!("go down"),
Direction::Left => println!("go left"),
Direction::Right => println!("go right"),
}
}
Structure
- A type that contains multiple pieces of fata
- All or nothing - cannot have same pieces of data and not others
- Each pieces of data is called a “field”
- Makes working with data easier
- Similar data can be grouped together
Recap
- Structure deal with multiple pieces of data
- All fields must be present to create a struct
- Fields can be accessed using a dot (.)
Demo : Structure
struct GroceryItem {
stock: i32,
price: f64,
}
fn main() {
let cereal = GroceryItem {
stock: 10,
price: 2.99,
};
println!("stock:{:?}", cereal.stock);
println!("price:{:?}", cereal.price);
}
enum Flavor {
Sparkling,
Sweet,
Fruity,
}
struct Drink {
flavor: Flavor,
fluid_oz: f64,
}
fn print_drink(drink: Drink) {
match drink.flavor {
Flavor::Sparkling => {
println!("sparkling");
}
Flavor::Sweet => {
println!("sweet");
}
Flavor::Fruity => {
println!("fruity");
}
}
println!("oz:{:?}", drink.fluid_oz);
}
fn main() {
let sweet = Drink {
flavor: Flavor::Sweet,
fluid_oz: 6.0,
};
print_drink(sweet);
}
Tuples
- A type of “record”
- Store data anonymously,No need to name fields
- Useful to return pairs of data from functions
- Can be “destructured” easily into variables
enum Access {
Full,
}
fn one_two_three() -> (i32, i32, i32) {
(1, 2, 3)
}
fn main() {
let numbers = one_two_three();
let (x, y, z) = one_two_three();
println!("{:?}, {:?}", x, numbers.0);
println!("{:?}, {:?}", y, numbers.1);
println!("{:?}, {:?}", z, numbers.2);
let (employee, access) = ("Jake", Access::Full);
}
Recap
- Allow for anonymous data access
- Useful when destructuring information into multiple variables
- Can contain any number of fields
- Use struct when more than 2 or 3 fields
Demo: Tuples
fn main() {
let coord = (2, 3);
println!("{:?}, {:?}", coord.0, coord.1);
let (x, y) = (2, 3);
println!("{:?}, {:?}", x, y);
let user_info = ("Emma", 20);
let (name, age) = user_info;
println!("name:{:?}, age:{:?}", name, age);
}
Expressions
- Rust is an expression-based language
- Most things are evaluated and return some value
- Expresion values coalesce to a single point
- Can be used for nesting logic
Recap
- Expressions allow nested logic
- if and match expressions can be nested
- Best to not use more than two or three levels
Demo expression
enum Menu {
Burger,
Fries,
Drink,
}
fn main() {
let my_num = 3;
let is_lt_t = if my_num < 5 { true } else { false };
let message = match my_num {
1 => "hello",
_ => "goodbye",
};
let item = Menu::Drink;
let drink_type = "water";
let order_placed = match item {
Menu::Drink => {
if drink_type == "water" {
true
} else {
false
}
}
_ => true,
};
}
Fundamentals : Intermediate Memory
Basic memory refresh
- Memory is stored using binary
- Bits: 0 or 1
- Computer optimized for bytes
- 1byte == 8 contiguous bits
- Fully contiguous
Addresses
- All data in memory has an “address”
- Used to locate data
- Always the same -only data changes
- Usually don’t utilize addresses directly
- Variables handle most of the work
Offsets
- Items can be located at an address using an “offset”
- Offsets begin at 0
- Represent the number of bytes aways from the original address
- Normally deal with indexes instead
offset - = 1 byte
0 1 2 3
address 0 - - - -
4 - + - - + address 4, offset 1
8 - - - -
12 - - - -
16 - - - -
Recap
- Memory uses addresses & offsets
- Addresses are permanent, data differs
- Offsets can be used to “index” into some data
Fundamentals : Ownership
Managing memory
- Programs must track memory
- If they fail to do so, a “leak” occurs
- Rust utilizes an “ownership” model to manage memory
- The “owner” of memory is responsible for cleaning up the memory
- Memory can either be “moved” or “borrowed”
Example - Move
enum Light {
Bright,
Dull,
}
fn display_light(light: Light) {
match light {
Light::Bright => println!("bright"),
Light::Dull => println!("dull"),
}
}
fn main() {
let dull = Light::Dull;
display_light(dull); // value moved here
// display_light(dull); //Error value used here after move
}
Example - Borrow
enum Light {
Bright,
Dull,
}
fn display_light(light: &Light) {
match light {
Light::Bright => println!("bright"),
Light::Dull => println!("dull"),
}
}
fn main() {
let dull = Light::Dull;
display_light(&dull);
display_light(&dull);
}
Recap
- Memory must be managed in some way to prevent leaks
- Rust uses “ownership” to accomplish memory management
- The “owner” of data must clean up the memory
- This occurs automatically at the end of the scope
- Default behavior is to “move” memory to a new owner
- Use an ampersand (&) to allow code to “borrow” memory
Activity impl
enum Color {
Brown,
Red,
}
impl Color {
fn print(&self) {
match self {
Color::Brown => println!("brown"),
Color::Red => println!("red"),
}
}
}
struct Dimensions {
width: f64,
height: f64,
depth: f64,
}
impl Dimensions {
fn print(&self) {
println!("width:{:?}", self.width);
println!("height:{:?}", self.height);
println!("depth:{:?}", self.depth);
}
}
struct ShippingBox {
color: Color,
weight: f64,
dimensions: Dimensions,
}
impl ShippingBox {
fn new(weight: f64, color: Color, dimensions: Dimensions) -> Self {
Self {
weight,
color,
dimensions,
}
}
fn print(&self) {
self.color.print();
self.dimensions.print();
println!("weight:{:?}", self.weight);
}
}
fn main() {
let small_dimensions = Dimensions {
width: 1.0,
height: 2.0,
depth: 3.0,
};
let small_box = ShippingBox::new(5.0, Color::Red, small_dimensions);
small_box.print();
}
Structure
- A type that contains multiple pieces of fata
- All or nothing - cannot have same pieces of data and not others
- Each pieces of data is called a “field”
- Makes working with data easier
- Similar data can be grouped together
Recap
- Structure deal with multiple pieces of data
- All fields must be present to create a struct
- Fields can be accessed using a dot (.)
Demo : Structure
struct GroceryItem {
stock: i32,
price: f64,
}
fn main() {
let cereal = GroceryItem {
stock: 10,
price: 2.99,
};
println!("stock:{:?}", cereal.stock);
println!("price:{:?}", cereal.price);
}
enum Flavor {
Sparkling,
Sweet,
Fruity,
}
struct Drink {
flavor: Flavor,
fluid_oz: f64,
}
fn print_drink(drink: Drink) {
match drink.flavor {
Flavor::Sparkling => {
println!("sparkling");
}
Flavor::Sweet => {
println!("sweet");
}
Flavor::Fruity => {
println!("fruity");
}
}
println!("oz:{:?}", drink.fluid_oz);
}
fn main() {
let sweet = Drink {
flavor: Flavor::Sweet,
fluid_oz: 6.0,
};
print_drink(sweet);
}
Tuples
- A type of “record”
- Store data anonymously,No need to name fields
- Useful to return pairs of data from functions
- Can be “destructured” easily into variables
enum Access {
Full,
}
fn one_two_three() -> (i32, i32, i32) {
(1, 2, 3)
}
fn main() {
let numbers = one_two_three();
let (x, y, z) = one_two_three();
println!("{:?}, {:?}", x, numbers.0);
println!("{:?}, {:?}", y, numbers.1);
println!("{:?}, {:?}", z, numbers.2);
let (employee, access) = ("Jake", Access::Full);
}
Recap
- Allow for anonymous data access
- Useful when destructuring information into multiple variables
- Can contain any number of fields
- Use struct when more than 2 or 3 fields
Demo: Tuples
fn main() {
let coord = (2, 3);
println!("{:?}, {:?}", coord.0, coord.1);
let (x, y) = (2, 3);
println!("{:?}, {:?}", x, y);
let user_info = ("Emma", 20);
let (name, age) = user_info;
println!("name:{:?}, age:{:?}", name, age);
}
Expressions
- Rust is an expression-based language
- Most things are evaluated and return some value
- Expresion values coalesce to a single point
- Can be used for nesting logic
Recap
- Expressions allow nested logic
- if and match expressions can be nested
- Best to not use more than two or three levels
Demo expression
enum Menu {
Burger,
Fries,
Drink,
}
fn main() {
let my_num = 3;
let is_lt_t = if my_num < 5 { true } else { false };
let message = match my_num {
1 => "hello",
_ => "goodbye",
};
let item = Menu::Drink;
let drink_type = "water";
let order_placed = match item {
Menu::Drink => {
if drink_type == "water" {
true
} else {
false
}
}
_ => true,
};
}
Fundamentals : Intermediate Memory
Basic memory refresh
- Memory is stored using binary
- Bits: 0 or 1
- Computer optimized for bytes
- 1byte == 8 contiguous bits
- Fully contiguous
Addresses
- All data in memory has an “address”
- Used to locate data
- Always the same -only data changes
- Usually don’t utilize addresses directly
- Variables handle most of the work
Offsets
- Items can be located at an address using an “offset”
- Offsets begin at 0
- Represent the number of bytes aways from the original address
- Normally deal with indexes instead
offset - = 1 byte
0 1 2 3
address 0 - - - -
4 - + - - + address 4, offset 1
8 - - - -
12 - - - -
16 - - - -
Recap
- Memory uses addresses & offsets
- Addresses are permanent, data differs
- Offsets can be used to “index” into some data
Fundamentals : Ownership
Managing memory
- Programs must track memory
- If they fail to do so, a “leak” occurs
- Rust utilizes an “ownership” model to manage memory
- The “owner” of memory is responsible for cleaning up the memory
- Memory can either be “moved” or “borrowed”
Example - Move
enum Light {
Bright,
Dull,
}
fn display_light(light: Light) {
match light {
Light::Bright => println!("bright"),
Light::Dull => println!("dull"),
}
}
fn main() {
let dull = Light::Dull;
display_light(dull); // value moved here
// display_light(dull); //Error value used here after move
}
Example - Borrow
enum Light {
Bright,
Dull,
}
fn display_light(light: &Light) {
match light {
Light::Bright => println!("bright"),
Light::Dull => println!("dull"),
}
}
fn main() {
let dull = Light::Dull;
display_light(&dull);
display_light(&dull);
}
Recap
- Memory must be managed in some way to prevent leaks
- Rust uses “ownership” to accomplish memory management
- The “owner” of data must clean up the memory
- This occurs automatically at the end of the scope
- Default behavior is to “move” memory to a new owner
- Use an ampersand (&) to allow code to “borrow” memory
Demo impl
Working With Data Option
- A type that may be one of two things
- Some data of a specified type
- Nothing
- Used in scenarios where data may not required or is unavailable
- Unable to find something
- Ran out of items in a list
- Form field not filled out
Definition
enum Option<T> {
Some(T),
None
}
Example
struct Customer {
age: Option<i32>,
email: String,
}
fn main() {
let mark = Customer {
age: Some(22),
email: "mark@example.com".to_owned(),
};
let becky = Customer {
age: None,
email: "becky@example.com".to_owned(),
};
match becky.age {
Some(age) => println!("customer is {:?} years old", age),
None => println!("customer age not provided"),
}
}
Recap
- Option represents either some data or nothing
- Some(variabkle_name)
- Data is available
- None
- No data is available
- Some(variabkle_name)
- Useful when needing to work with optional data
- Use Option to declare an optional type
Activity Option
struct Student {
name: String,
locker: Option<i32>,
}
fn main() {
let mary = Student {
name: "Mary".to_owned(),
locker: Some(3),
};
println!("student: {:?}", mary.name);
match mary.locker {
Some(num) => println!("locker number is: {:?}", num),
None => println!("no locker assigned"),
}
}
Demo Documentation
/// A favorite color
enum Color {
Red,
Blue,
}
/// A piece of mail
struct Mail {
/// The destination address
address: String,
}
/// Add two numbers together
fn add(a:i32, b:i32) ->i32 {
a + b
}
fn main() {
}
cargo doc --open
Demo Standard Library
rustup doc
Working With Data Result
- A data type that contains one of two types of data
- “Successful” data
- “Error” data
- Used in scenarios where an action needs to be taken, but has the possibility of failure
- Copying a file
- Connecting to a website
enum Result<T, E> {
OK(T),
Err(E),
}
Recap
- Result represents either success or failure
- OK(variable_name)
- The operation was completed
- Error(variable_name)
- The operation failed
- OK(variable_name)
- Useful when working with functionality that can potentially fail
- Use Result<T, E> when working with results
Demo Result
#[derive(Debug)]
enum MenuChoice {
MainMenu,
Start,
Quit,
}
fn get_choice(input: &str) -> Result<MenuChoice, String> {
match input {
"mainmenu" => Ok(MenuChoice::MainMenu),
"start" => Ok(MenuChoice::Start),
"quit" => Ok(MenuChoice::Quit),
_ => Err("menu choice not found".to_owned()),
}
}
fn print_choice(choice: &MenuChoice) {
println!("choice = {:?}", choice);
}
fn pick_choice(input: &str) -> Result<(), String> {
let choice:MenuChoice = get_choice(input)?;
print_choice(&choice);
Ok(())
}
fn main() {
let choice = get_choice("mainmenu");
match choice {
Ok(inner_choice) => print_choice(&inner_choice),
Err(err) => println!("{}", err),
}
pick_choice("start");
let choice = pick_choice("end");
println!("choice value = {:?}", choice);
}
Activity Result and the question mark operator
enum Position {
Maintenance,
Marketing,
Manager,
LineSupervisor,
KitchenStaff,
AssemblyTech,
}
enum Status {
Active,
Terminated,
}
struct Employee {
position: Position,
status: Status,
}
fn try_access(employee: &Employee) -> Result<(), String> {
match employee.status {
Status::Terminated => return Err("terminated".to_owned()),
_ => (),
}
match employee.position {
Position::Maintenance => Ok(()),
Position::Marketing => Ok(()),
Position::Manager => Ok(()),
_ => Err("invalid position".to_owned()),
}
}
fn print_access(employee: &Employee) -> Result<(), String> {
let attemp_access = try_access(employee)?;
println!("access ok");
Ok(())
}
fn main() {
let manager = Employee {
position: Position::Manager,
status: Status::Active,
};
match print_access(&manager) {
Err(e) => println!("access denied: {:?}", e),
_ => (),
};
}
Data Structures Hashmap
- Collection that stores data as key-value pairs
- Data is located using the “key”
- The data is the “value”
- Similar to definitions in a dictionary
- Very fast to retrieve data using the key
use std::collections::HashMap;
fn main() {
let mut people = HashMap::new();
people.insert("Susan", 21);
people.insert("Ed", 13);
people.insert("Will", 14);
people.insert("Cathy", 22);
people.remove("Susan");
match people.get("Ed") {
Some(age) => println!("age = {:?}", age),
None => println!("not found"),
}
// iterate
for (person, age) in people.iter() {
println!("person = {:?} age = {:?}", person, age);
}
for person in people.keys() {
println!("person = {:?}", person);
}
for age in people.values() {
println!("age = {:?}", age);
}
}
Demo Basic Closures
fn main() {
let add = |a: i32, b: i32| -> i32 { a + b };
let add = |a, b| a + b;
let sum = add(2, 1);
}
Activity Map combinator
#[derive(Debug)]
struct User{
user_id:i32,
name: String,
}
fn find_user(name:&str) -> Option<i32> {
let name = name.to_lowercase();
match name.as_str() {
"sam"=>Some(1),
"matt"=>Some(5),
"katie"=>Some(9),
_=>None
}
}
fn main() {
let user_name = "sam";
let user = find_user(user_name)
.map(|user_id|
User{
user_id: user_id,
name: user_name.to_owned()
}
);
match user {
Some(user)=> println!("{:?}", user),
None => println!("user not found"),
}
}
Demo Option Combinators
fn main() {
let a:Option<i32> = Some(1);
dbg!(a);
let a_is_some = a.is_some();
dbg!(a_is_some);
let a_is_none = a.is_none();
dbg!(a_is_none);
let a_mapped = a.map(|num| num + 1);
dbg!(a_mapped);
let a_filtered = a.filter(|num| num == &1);
dbg!(a_filtered);
let a_or_else = a.or_else(|| Some(5));
dbg!(a_or_else);
let unwrapped = a.unwrap_or_else(||0);
dbg!(unwrapped);
}
Activity Iterator
fn main() {
let data: Vec<_> = vec![1, 2, 3, 4, 5]
.into_iter()
.map(|num| num * 3)
.filter(|num| num > &10)
.collect();
for num in data {
println!("{}", num);
}
}
Demo Range
fn main() {
let range = 1..=3;
let range = 1..4;
for num in 1..4 {
println!("{:?}", num);
}
for ch in 'a'..='f' {
println!("{:?}", ch);
}
}
Demo if let
enum Color {
Red,
Blue,
Green,
}
fn main() {
let maybe_user = Some("jerry");
if let Some(user) = maybe_user {
println!("user={:?}", user);
}
let red = Color::Red;
if let Color::Red = red {
println!("its red");
} else {
println!("its not red");
}
}
Demo while let
enum Color {
Red,
Blue,
Green,
}
fn main() {
let mut data = Some(3);
while let Some(i) = data {
println!("loop");
data = None;
}
let numbers = vec![1, 2, 3, 4, 5];
let mut number_iter = numbers.iter();
while let Some(num) = number_iter.next() {
println!("num = {:?}", num);
}
println!("done")
}
Demo Modules
mod greet {
pub fn hello() {
println!("Hello");
}
pub fn goodbye() {
println!("goodbye");
}
}
mod math {
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
pub fn sub(a: i32, b: i32) -> i32 {
a - b
}
}
fn main() {
use greet::hello;
hello();
greet::goodbye();
math::add(1, 2);
math::sub(1, 2);
}
Activity Inline Modules
mod msg {
pub fn trim(msg: &str) -> &str {
msg.trim()
}
pub fn capitalize(msg: &str) -> std::borrow::Cow<'_, str> {
if let Some(letter) = msg.get(0..1) {
format!("{}{}", letter.to_uppercase(), &msg[1..msg.len()]).into()
} else {
msg.into()
}
}
pub fn exciting(msg: &str) -> String {
format!("{}", msg)
}
}
mod math {
pub fn add(lhs: isize, rhs: isize) -> isize {
lhs + rhs
}
pub fn sub(lhs: isize, rhs: isize) -> isize {
lhs - rhs
}
pub fn mul(lhs: isize, rhs: isize) -> isize {
lhs * rhs
}
}
fn main() {
let result = {
let two_plus_two = math::add(2, 2);
let three = math::sub(two_plus_two, 1);
math::mul(three, three)
};
assert_eq!(result, 9);
println!("(2 + 2 - 1) * 3 = {}", result);
{
use msg::{capitalize, exciting, trim};
let hello = {
let msg = "hello";
let msg = trim(msg);
capitalize(msg)
};
let world = {
let msg = "world";
exciting(msg)
};
let msg = format!("{}, {}", hello, world);
assert_eq!(&msg, "Hello, world");
println!("{}", msg);
}
}
Demo Testing
fn all_caps(word: &str) -> String {
word.to_uppercase()
}
fn main() {}
#[cfg(test)]
mod test {
use crate::*;
#[test]
fn check_all_caps() {
let result = all_caps("hello");
let expected = String::from("HELLO");
assert_eq!(result, expected, "string should be all uppercase");
}
#[test]
fn check_all_cap_test() {
let result = all_caps("hello");
let expected = String::from("world");
assert_eq!(result, expected, "string should be all uppercase");
}
}
运行 cargo test 查看测试结果
running 2 tests
test test::check_all_caps ... ok
test test::check_all_cap_test ... FAILED
failures:
---- test::check_all_cap_test stdout ----
thread 'test::check_all_cap_test' panicked at 'assertion failed: `(left == right)`
left: `"HELLO"`,
right: `"world"`: string should be all uppercase', src/main.rs:23:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
test::check_all_cap_test
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--bin rust_demo`
Activity Testing
/// ensures n is >= lower and <= upper
fn clamp(n: i32, lower: i32, upper: i32) -> i32 {
if n < lower {
lower
} else if n > upper {
upper
} else {
n
}
}
/// divides a and b
fn div(a: i32, b: i32) -> Option<i32> {
if b == 0 {
None
} else {
Some(a / b)
}
}
/// takes two strings and places them immediately one after another
fn concat(first: &str, second: &str) -> String {
format!("{} {}", first, second)
}
fn main() {}
#[cfg(test)]
mod test {
use crate::*;
#[test]
fn clamp_lower() {
let result = clamp(10, 100, 1000);
let expected = 100;
assert_eq!(result, expected, "should be 100");
}
#[test]
fn clamp_upper() {
let result = clamp(5000, 100, 1000);
let expected = 1000;
assert_eq!(result, expected, "should be 500");
}
#[test]
fn check_div() {
let result = div(1, 1);
let expected = Some(1);
assert_eq!(result, expected, "should be 1");
}
#[test]
fn check_div_zero() {
let result = div(1, 0);
let expected = None;
assert_eq!(result, expected, "cannot divide zero");
}
#[test]
fn check_concat() {
let result = concat("a", "b");
let expected = String::from("ab");
assert_eq!(result, expected, "should be placed immediately adjacent");
}
}
Demo External Crates
the dependency section (Cargo.toml) is where we will include our external crates
[dependencies]
humantime = "2.1.0"
use humantime::format_duration;
use std::time::Duration;
fn main() {
let d = Duration::from_secs(9876);
println!("{}", format_duration(d));
let val1 = Duration::new(9420, 0);
assert_eq!(format_duration(val1).to_string(), "2h 37m");
let val2 = Duration::new(0, 32_000_000);
assert_eq!(format_duration(val2).to_string(), "32ms");
}