Study Topic: Language: Rust
This is a Study Leave topic.
Language Notes
Samples
temp
use std::io; fn main() { let mut input = String::new(); io::stdin().read_line(&mut input) .expect("Failed to read line"); let input = "-272C"; let scale = input.get(input.len()-1..).unwrap_or("F"); println!("Last {}", scale); let input: f32 = input.get(..input.len()-1).unwrap_or("0").parse() .expect("Please type a number!"); println!("Input {}", input); if scale == "F" { let trans = (input - 32.0) * 5.0 / 9.0; println!("Answer {0}F = {1}C", input, trans); } else if scale == "C" { let trans = (input * 9.0 / 5.0) + 32.0; println!("Answer {0}C = {1}F", input, trans); } else { panic!("Unknown scale: '{}'", scale); } }
fib
use std::io; fn main() { let mut input = String::new(); io::stdin().read_line(&mut input) .expect("Failed to read line"); let input = "30"; let input: u64 = input.parse() .expect("Please type a number!"); println!("Input {}", input); let output = calc_fib(input); println!("Output {}", output); let output2 = calc_fib2(input); println!("Output2 {}", output2); } fn calc_fib(n : u64) -> u64 { if n == 0 || n == 1 { return 1; } return calc_fib(n-1) + calc_fib(n-2); } fn calc_fib2(n : u64) -> u64 { let mut f = (1, 1); for _ in 1..n { f = (f.1, f.0 + f.1); } return f.1; }
Ownership
- No ref => transfers ownership
- & => immutable ref
- &mut => mutable ref
Can have n-immutable OR 1-mutable,but not both
Structs
definition - with debug output support
#[derive(Debug)] struct Rectangle { width: u32, height: u32, }
memfunc with no parameters
impl Rectangle { fn area(&self) -> u32 { self.width * self.height } }
memfunc with parameter
impl Rectangle { fn can_hold(&self, other: &Rectangle) -> bool { self.width > other.width && self.height > other.height } }
static func
impl Rectangle { fn square(size: u32) -> Rectangle { Rectangle { width: size, height: size } } }
Enums
A rich notion, allowing per-enum type information
enum IpAddr { V4(u8, u8, u8, u8), V6(String), } let home = IpAddr::V4(127, 0, 0, 1); let loopback = IpAddr::V6(String::from("::1"));
Can also have methods
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();
The Option enum is very prevalent and replaces null
enum Option<T> { Some(T), None, }
Use match (which is exhaustive) to process enums
fn plus_one(x: Option<i32>) -> Option<i32> { match x { None => None, Some(i) => Some(i + 1), } }
"_" placeholder offers default
let some_u8_value = 0u8; match some_u8_value { 1 => println!("one"), 3 => println!("three"), 5 => println!("five"), 7 => println!("seven"), _ => (), }
"if let" allows concise matching of one case
let some_u8_value = Some(0u8); if let Some(3) = some_u8_value { println!("three"); }
Modules
You can nest
mod network { fn connect() { } mod client { fn connect() { } } }
A summary of the rules of modules with regard to files:
- If a module named foo has no submodules, you should put the declarations for foo in a file named foo.rs.
- If a module named foo does have submodules, you should put the declarations for foo in a file named foo/mod.rs.
Use pub for public visibility on modules and functions
Namespacing: use super to avoid root-based paths
Collection Classes
Vectors
&v for normal
let v = vec![100, 32, 57]; for i in &v { println!("{}", i); }
&mut v for in-place changes
let mut v = vec![100, 32, 57]; for i in &mut v { *i += 50; }
Use enum to get multi-typed contents
Strings
UTF-8 so not a char-array
char-iteration
for c in "नमस्ते".chars() { println!("{}", c); }
byte-iteration
for b in "नमस्ते".bytes() { println!("{}", b); }
Hash Maps
Main points:
- takes ownership of contained items
- can iterate via "for (key, value) in &scores {"
- use insert to overwrite, and entry(key).or_insert(value) to fill conditionally
Can mutate as we iterate:
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); *count += 1; } println!("{:?}", map);
Error Handling
panic!
- Will ditch the program
- Unwinds by default, but can set to abort in Cargo.toml
- Use "RUST_BACKTRACE=1" to get a backtrace
Result
enum Result<T, E> { Ok(T), Err(E), }
- "unwrap" for we-expect-this-to-work-and-generic-panic-is-ok
- "expect" for more custom panic message
- Use "?" as shorthand for propagating Result
use std::io; use std::io::Read; use std::fs::File; 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) }
When To Use What
Use panic:
- examples
- protoypes
- tests
- some assumption, guarantee, contract, or invariant has been broken
- e.g. preconditions on type constructors
Generics
The basic idea is familiar:
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()); }
Traits
Like C++ concepts
pub trait Summarizable { fn summary(&self) -> String; }
Can have default implementations
Can then use in generics
pub fn notify<T: Summarizable>(item: T) { println!("Breaking news! {}", item.summary()); }
Can do partial matching so only those eligible can use the method
use std::fmt::Display; struct Pair<T> { x: T, y: T, } impl<T> Pair<T> { fn new(x: T, y: T) -> Self { Self { x, y, } } } impl<T: Display + PartialOrd> Pair<T> { fn cmp_display(&self) { if self.x >= self.y { println!("The largest member is x = {}", self.x); } else { println!("The largest member is y = {}", self.y); } } }
Can also do "blanket implementations" for every type satisfying the trait(s):
impl<T: Display> ToString for T { // --snip-- }
Lifetimes
Basic Syntax
Apostrophe followed by (short) name
&i32 // a reference &'a i32 // a reference with an explicit lifetime &'a mut i32 // a mutable reference with an explicit lifetime
Example - here the result is only usable when both the inputs are usable
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }
If a struct is using a borrowed data type, that will need a lifetime
struct ImportantExcerpt<'a> { part: &'a str, }
Lifetimes and generics are both specified in angle brackets for types
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 } }
Compiler Elision
The compiler does a lot of lifetime elision compared to pre-1.0 Rust
- 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.
Static
There is a special full-program lifetime: 'static
let s: &'static str = "I have a static lifetime.";
Tests
Writing Tests
Tests fail with panic!
Use assert!, assert_eq!, assert_ne!
Put tests inside modules, at the bottom
#[cfg(test)] mod tests { #[test] fn it_works() { assert_eq!(2 + 2, 4); } }
Use #[should_panic(expected="expected text")] attribute if code should panic
Running Tests
Run with "cargo test"
By default will run parallel, use "cargo test -- --test-threads=1" to set to single
Use "cargo test -- --nocapture" to see stdout
Use "cargo test xx" to run all tests with "xx" in their name
Use "cargo test -- --ignored" to include tests with the #[ignore] attribute
Test Organisation
Tests can access private functions in the same module (via standard Rust module rules)
Integration test: add "tests" directory alongside "src" and everything there with #[test] attribute will be run by "cargo test"
Each module in "tests" is compiled as a separate crate
To run a given integration test module, do "cargo test --test module_name"
Put shared (non-test) routines in "tests/common/mod.rs" rather than "tests/common.rs"
You can't run tests on binary crates. This implies binary crates should be trivial stubs that invoke the library crates.
Closures
Similar to functions:
fn add_one_v1 (x: u32) -> u32 { x + 1 } let add_one_v2 = |x: u32| -> u32 { x + 1 }; let add_one_v3 = |x| { x + 1 }; let add_one_v4 = |x| x + 1 ;
An example of a simple cache closure:
struct Cacher<T> where T: Fn(u32) -> u32 { calculation: T, value: Option<u32>, } impl<T> Cacher<T> where T: Fn(u32) -> u32 { fn new(calculation: T) -> Cacher<T> { Cacher { calculation, value: None, } } fn value(&mut self, arg: u32) -> u32 { match self.value { Some(v) => v, None => { let v = (self.calculation)(arg); self.value = Some(v); v }, } } } // Then call with a closure like this... let mut expensive_result = Cacher::new(|num| { println!("calculating slowly..."); thread::sleep(Duration::from_secs(2)); num });
Instead of "Fn", can use "FnOnce" for single capture, "FnMut" for mutable borrowing -> can change environment.
Can force closure to take ownership with "move" keyword;
fn main() { let x = vec![1, 2, 3]; let equal_to_x = move |z| z == x; // will not compile as ownership has been passed on println!("can't use x here: {:?}", x); let y = vec![1, 2, 3]; assert!(equal_to_x(y)); }
Iterators
Usage:
let v1 = vec![1, 2, 3]; let v1_iter = v1.iter(); for val in v1_iter { println!("Got: {}", val); }
Trait to implement:
trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>; // methods with default implementations elided }
Methods: map, filter, collect, ...
Simple example:
struct Counter { count: u32, } impl Counter { fn new() -> Counter { Counter { count: 0 } } } impl Iterator for Counter { type Item = u32; fn next(&mut self) -> Option<Self::Item> { self.count += 1; if self.count < 6 { Some(self.count) } else { None } } } #[test] fn calling_next_directly() { let mut counter = Counter::new(); assert_eq!(counter.next(), Some(1)); assert_eq!(counter.next(), Some(2)); assert_eq!(counter.next(), Some(3)); assert_eq!(counter.next(), Some(4)); assert_eq!(counter.next(), Some(5)); assert_eq!(counter.next(), None); }
Exmaple of filter usage:
// before... pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { let mut results = Vec::new(); for line in contents.lines() { if line.contains(query) { results.push(line); } } results } // after... pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { contents.lines() .filter(|line| line.contains(query)) .collect() }
Targets
2nd edition book
Links
- Book at Rust Book (2nd Ed)
Tasks
- 1. Introduction [DONE]
- 1.1. Installation
- 1.2. Hello, World!
- 2. Guessing Game Tutorial [DONE]
- 3. Common Programming Concepts
- 3.1. Variables and Mutability
- 3.2. Data Types
- 3.3. How Functions Work
- 3.4. Comments
- 3.5. Control Flow
- 4. Understanding Ownership [DONE]
- 4.1. What is Ownership?
- 4.2. References & Borrowing
- 4.3. Slices
- 5. Using Structs to Structure Related Data [DONE]
- 5.1. Defining and Instantiating Structs
- 5.2. An Example Program Using Structs
- 5.3. Method Syntax
- 6. Enums and Pattern Matching [DONE]
- 6.1. Defining an Enum
- 6.2. The match Control Flow Operator
- 6.3. Concise Control Flow with if let
- 7. Modules [DONE]
- 7.1. mod and the Filesystem
- 7.2. Controlling Visibility with pub
- 7.3. Referring to Names in Different Modules
- 8. Common Collections [DONE]
- 8.1. Vectors
- 8.2. Strings
- 8.3. Hash Maps
- 9. Error Handling [DONE]
- 9.1. Unrecoverable Errors with panic!
- 9.2. Recoverable Errors with Result
- 9.3. To panic! or Not To panic!
- 10. Generic Types, Traits, and Lifetimes
- 10.1. Generic Data Types [DONE]
- 10.2. Traits: Defining Shared Behavior [DONE]
- 10.3. Validating References with Lifetimes [DONE]
- 11. Testing [DONE]
- 11.1. Writing tests
- 11.2. Running tests
- 11.3. Test Organization
- 12. An I/O Project: Building a Command Line Program [DONE]
- 12.1. Accepting Command Line Arguments
- 12.2. Reading a File
- 12.3. Refactoring to Improve Modularity and Error Handling
- 12.4. Developing the Library’s Functionality with Test Driven Development
- 12.5. Working with Environment Variables
- 12.6. Writing Error Messages to Standard Error Instead of Standard Output
- 13. Functional Language Features in Rust [DONE]
- 13.1. Closures
- 13.2. Iterators
- 13.3. Improving our I/O Project
- 13.4. Performance
- 14. More about Cargo and Crates.io
- 14.1. Customizing Builds with Release Profiles
- 14.2. Publishing a Crate to Crates.io
- 14.3. Cargo Workspaces
- 14.4. Installing Binaries from Crates.io with cargo install
- 14.5. Extending Cargo with Custom Commands
- 15. Smart Pointers
- 15.1. Box<T> Points to Data on the Heap and Has a Known Size
- 15.2. The Deref Trait Allows Access to the Data Through a Reference
- 15.3. The Drop Trait Runs Code on Cleanup
- 15.4. Rc<T>, the Reference Counted Smart Pointer
- 15.5. RefCell<T> and the Interior Mutability Pattern
- 15.6. Creating Reference Cycles and Leaking Memory is Safe
- 16. Fearless Concurrency
- 16.1. Threads
- 16.2. Message Passing
- 16.3. Shared State
- 16.4. Extensible Concurrency: Sync and Send
- 17. Is Rust an Object-Oriented Programming Language?
- 17.1. What Does Object-Oriented Mean?
- 17.2. Trait Objects for Using Values of Different Types
- 17.3. Object-Oriented Design Pattern Implementations
- 18. Patterns Match the Structure of Values
- 18.1. All the Places Patterns May be Used
- 18.2. Refutability: Whether a Pattern Might Fail to Match
- 18.3. All the Pattern Syntax
- 19. Advanced Features
- 19.1. Unsafe Rust
- 19.2. Advanced Lifetimes
- 19.3. Advanced Traits
- 19.4. Advanced Types
- 19.5. Advanced Functions & Closures
- 20. Final Project: Building a Multithreaded Web Server
- 20.1. A Single Threaded Web Server
- 20.2. How Slow Requests Affect Throughput
- 20.3. Designing the Thread Pool Interface
- 20.4. Creating the Thread Pool and Storing Threads
- 20.5. Sending Requests to Threads Via Channels
- 20.6. Graceful Shutdown and Cleanup