CMSC330

Rust

Rust

Slices
Structs
Traits
Boxes and RC

Slices

Slices

Recall the following memory issue


char* str = "hello world";
char* sub = *str[5]
free(str);
printf("%s",sub);
          

Similar in Rust


let x = String::from ("Hello World");
let y = &x[6..10];
drop(x);
println!("{}",y);
          

Rust will not compile this

Slices reference a part of a continuous piece of memeory


let x = String::from ("Hello World");
let hello = &s[0..5];
let world = &s[6..11];

x: String
  ptr -> Hello World
  len: 11
  capacity: 11

hello: &str
  ptr -> x.ptr
  len: 5

hello: &str
  ptr -> x.ptr[6]
  len: 5
          

If x is dropped, so is everything else

x cannot be modifed while slices exist

Slices reference a part of a continuous piece of memeory


          let a = [1,2,3,4];
          let v = Vec::new();;
          v.push(1);
          let b = &a[1..3]
          let w = &v[..2]
          

Arrays and vectors also have slices

Structs


struct User {
    username: String,
    active: bool,
}
let user1 = User {
  username: String::from("clyff"),
  active: true,
}
println!("{}",user1.username);
          

Mutability describes a variable, not the data


let mut x = User { ... };
x.active = false;
          

struct User {
    username: String,
    active: bool,
}
let user1 = User {
  username: String::from("clyff"),
  active: true,
}
println!("{}",user1.username);
          

Structs have associated functions


struct User {
    username: String,
    active: bool,
}

impl User{
  fn is_online(&self){
    self.active
  }
}
          

Enums also are helpful for custom data types


enum Color{
  Green,
  Red,
  Blue,
  Other(String)
}

let r = Color::Red;
let o = Color::Other(String::from("Grey"));
          

Enums also have associated functions


impl Color{
  fn is_green(&self)->bool{
    match self{
      Color::green => true,
      _ => false,
    }
  }
}
          

Both Enums and Structs allow for Generics


struct Point<T>{
  x:T,
  y:T,
}

enum option<T>{
  None,
  Some(T),
}
          

Same for associated Functions


impl<T > Point<T>{
  fn x(&self) -> &T{
    &self.x
  }
}
          

Hashmaps are built into Rust


let x = HashMap<i32,String>::new();
          

Traits

Like an Java intrerface


pub trait Printable{
  fn print(&self) -> String;
}

impl Printable for User{
  fn print(&self){
    format!("{}:{}",self.username,self.active)
  }
}

impl Printable for Point{
  fn print(&self){
    format!("({},{})",self.x,self.y)
  }
}
          

default implemention: so also like abstract class


pub trait Printable{
  fn print(&self) -> String{
    "I was supposed to have this overridden"
  }
}
          

Can have multiple and on a generic


struct Point<T:Clone + PartialOrd>{
  x:T,
  y:T,
}
          

Common Traits

  • Copy
  • Clone
  • Move
  • Deref
  • Display

Lifetimes are an implicit generic

Lifetime: scope for which a reference is valid


        let r;
        {
            let x = 5;
            r = &x;
        }
        println!("r: {}", r);
          

r and x have different lifetimes

Lifetimes are an implicit generic


fn longest(x: &str, y: &str) -> &str {
  if x.len() > y.len() {
    x
  } else {
    y
  }
}

Return type has no gaurantee it's a valid reference


fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
  if x.len() > y.len() {
    x
  } else {
    y
  }
}

struct ImportantString<'a>{
  istring: &'a str,
}
          

The instance of ImportantString cannot outlive the reference it holds


let x = String::from("Hello world");
{
  let y = ImportantString {istring:&x };
}
          

Boxes and RC


enum Tree{
  Leaf,
  Node(i32,Tree,Tree),
}
          

Issue: compiler does not know the size of Tree


enum Tree{
  Leaf,
  Node(i32,Box<Tree>,Box<Tree>),
}
          

References are pointers


let x = 5;
let y = &x;

assert_eq!(5, x);
assert_eq!(5, *y);
assert_eq!(5, y); //error here
          

Why do we not need to unpack the Box first?


let x = 5;
let y = Box::new(&x);

assert_eq!(5, x);
assert_eq!(5, *y);
          

Box has Deref and Drop traits

We can use this for functions


fn f(x: &str){
  println!("echoing {x}")
}

let a = Box::new(String::from("echo..."));
f(&a);
          

Use DerefMut for mutability

(Cannot go from immut to mut)

Sometimes Box is not sufficient


let a = Node(3, Box::new(Box::new(Nil)),Box::new(Nil));
let b = Node(2, Box::new(Box::new(Nil)),Box::new(Nil));
let c = Node(1, Box::new(a),Box::new(b));
let d = Node(4, Box::new(a),Box::new(a)); //not ok
          

We can fix with RC (Reference counting)

We can fix with RC (Reference counting)

Allows for multiple owners with trade off of potential memory leaks


enum Tree{
  Node(i32,Rc<Tree>,Rc<Tree>),
  Nil
}
let a = Tree(3, Rc::new(Rc::new(Nil)),Rc::new(Nil));
let b = Tree(2, Rc::new(Rc::new(Nil)),Rc::new(Nil));
let c = Tree(1, Rc::clone(&a),Rc::clone(&b));
let d = Cons(4, Rc::clone(&a),Rc::clone(&a)); //now ok
          

Has all tradeoffs of RC

(like cyclic structures)