Some Rust notes II
- A simple example of the syntax of specifying generic type parameters, trait bounds, and lifetimes:
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 } }
- We can put expected panic information in the
expected
parameter of#[should_panic]
:#[should_panic(expected = "Guess value must be less than or equal to 100")]
FnOnce
consumes the variables it captures from the closure’s environment. To consume the captured variables, the closure must take ownership of these variables and move them into the closure when it is defined. The Once part of the name represents the fact that the closure can’t take ownership of the same variables more than once, so it can be called only once.FnMut
can change the environment because it mutably borrows values.Fn
borrows values from the environment immutably.- An example of using a closure function as a element in a struct:
struct Cacher<T> where T: Fn(u32) -> u32 { calculation: T, value: HashMap<u32, u32>, } impl<T> Cacher<T> where T: Fn(u32) -> u32 { fn new(calculation: T) -> Cacher<T> { Cacher { calculation, value: HashMap::new(), } } fn value(&mut self, arg: u32) -> u32 { match self.value.get(&arg) { Some(v) => *v, None => { let v = (self.calculation)(arg); self.value.insert(arg, v); v } } } }
- Using
sum
method foriterator
will take ownership so that if this statement is called:let v1 = vec![1, 2, 3]; let v1_iter = v1.iter(); let total: i32 = v1_iter.sum();
we cannot use v1_iter after the call because of the reason stated above.
- The default value for the opt-level setting for the dev profile is 0 and the one for the release profile is 3. Add these two lines in
Cargo.toml
will override the default optimization level fordev
profile:[profile.dev] opt-level = 1
- A
cons list
example written usingBox<T>
:enum List { Cons(i32, Box<List>), Nil, } use self::List::{Cons, Nil}; fn main() { let test = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))); }
- An example of creating a simple smart pointer implementing
Deref
andDrop
traits:struct MyBox<T>(T); impl<T> MyBox<T> { fn new(x: T) -> MyBox<T> { MyBox(x) } } impl<T> Deref for MyBox<T> { type Target = T; fn deref(&self) -> &T { &self.0 } } impl<T> Drop for MyBox<T> { fn drop(&mut self) { println!("Dropping MyBox!"); } }
- Comparing with regular reference, with
Rc
we can create things with shared ownership and do not need to specify lifetime parameters. - An example of combination of
Rc
andRefCell
:use std::rc::Rc; use std::cell::RefCell; #[derive(Debug)] enum List { Cons(Rc<RefCell<i32>>, Rc<List>), Nil, } use crate::List::{Cons, Nil}; fn main() { let value = Rc::new(RefCell::new(5)); let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil))); let b = Cons(Rc::new(RefCell::new(6)), Rc::clone(&a)); let c = Cons(Rc::new(RefCell::new(12)), Rc::clone(&a)); *value.borrow_mut() += 10; println!("{:?}\n{:?}\n{:?}\n", a, b, c); }
- By using
Rc<T>
andRefCell<T>
, it’s possible to create reference cycles which creates memory leaks because the reference count of each item in the cycle will never reach 0, and the values will never be dropped. - An example of using
Weak<T>
to avoid reference cycle and create a simple node structure: ```rust struct Node { value: i32, parent: RefCell<Weak>, children: RefCell<Vec<Rc >>, } fn main() { let root = Rc::new(Node { value: 0, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![]), });