CS3210 Design Operating Systems

Taesoo Kim





Rust 2: Ownership and Lifetime

Taesoo Kim

Administrivia

Q&A

  1. About Rust’s safety and trade-offs:

Is an OS made in Rust still susceptible to as many CVEs as an OS made in C or C++? If not, what is the tradeoff (lines of code, performance e.g. HLL tax, etc.) to make it safer?

  1. What’s POSIX (see, POSIX)?
  2. About Rust vs. GC-based HLL or C in performance? (→ Lec 3)
  3. About Rust vs. other approaches in memory safety? (→ Lec 2)
  4. How do macros work? (→ Lec 4 or Lec 8)

Summary: Thinking of Rust

Agenda

Type, Variable and Value

//
//      +--> variable name (or identifier)
//      |
let mut x: i32 = 1;
//  +--    ---   +-> value
//  |      |
//  |      +--> type (32-bit signed integer)
//  +--> noting mutability
//
x = 2;

Trick to ask Rustc about a type of variable

let mut x: i32 = 1;

let _: () = 1; // what's _ and ()?
let _: () = x;

Variable and type

Value and type

Getting the size of variable and type

let x: i32 = 1;
let y: String = String::from("hello");

dbg!(std::mem::size_of_val(&x));     //=>  4
dbg!(std::mem::size_of_val(&y));     //=> 24

dbg!(std::mem::size_of::<i32>());    //=>  4
dbg!(std::mem::size_of::<String>()); //=> 24

Strongly typed programming language

// x stores a value (1 :i32)
let mut x: i32 = 1;

// x is assigned with a value (2 :i32)
x = 2;

Assigning a value to a variable

Ownership and move semantics

let mut x = String::from("Hello!");
let y = String::from("World!");

// y's value is moved to, now x owns "World!"
x = y;

Behind the move operation

{
    // x <- alloc("Hello!")
    let mut x = String::from("Hello!");

    // y <- alloc("World!")
    let y = String::from("World!");

    // drop(x): free("Hello!")
    x = y;

    ...
} 
// drop(x): free("World!")

1) Behind the move: allocation

2) Behind the move: drop and copy

3) Out-of-scope: drop

ORBM: Ownership Based Resource Mgmt

Re-using uninitialized variables

let y = String::from("World!");

x = y;

let y = String::from("New World!");
let mut y = String::from("World!");

x = y;

y = String::from("New World!");

Example: transferring ownership

// move a value from the caller to the callee
fn take(s: String) -> {}

// move a value from the callee to the caller
fn new() -> String { String::from("Hello") }

Example: transferring ownership

fn take(s: String) -> {}

Example: transferring ownership

fn new() -> String { String::from("Hello") }

Ownership and copy semantics

let x = String::from("Hello!");
let y = x.clone();

Ownership and copy semantics

let x = String::from("Hello!");
let y = x.clone();

// @liballoc/string.rs
impl Clone for String {
  fn clone(&self) -> Self {
    String { vec: self.vec.clone() }
  }
}

Ownership and copy semantics

let x = String::from("Hello!");
let y = x.clone();

Copy semantics

let x: i32 = 10;
let y = x; // copied! (not moved)

dbg!(x); // still readable!
dbg!(y);

Copy marker trait

// for custom types
#[derive(Copy, Clone)]
struct Foo;

// @core/marker.rs
impl Copy for u64 {}

impl_copy! {
  usize u8 u16 u32 u64 u128
  isize i8 i16 i32 i64 i128
  f32 f64
  bool char
}

How to program with this language?

fn len(s: String) -> usize { ... }

let x = String::from("Hello");

// move, so unable to access x after
let y = len(x);

// copy, so need to keep copying!
let y = len(x.clone())
dbg!(y);

How to program with this language?

fn len(s: String) -> (usize, String) { ... }

let x = String::from("Hello");

// transferring the ownership back-and-force
let (y, x) = len(x);

dbg!(x);
dbg!(y);

How String::len() is implemented then?

// @liballoc/string.rs
impl String {
    ...
    pub fn len(&self) -> usize {
        self.vec.len()
    }
}

Borrowing via references

let x = String::from("Hello");

// => String::len(&x) -> usize
let y = x.len();

// x is still accessible!
dbg!(x);

Note. . has lots of magic behind the scene (see, Nomicon 4.3 The Dot Operator)

Thinking of shared/mutable references

// => String::len(&x) -> usize
x.len();

// => String::push_str(&mut x, ...)
x.push_str("World!");

// @libcore/marker.rs
// &T follows copy semantics
impl Copy for &T {}

Rule 0. Borrowing via references

  1. The owner should outlive all borrowers
let x = String::from("Hello");
let y = &x; // borrow a value for reading
let z = &x; // borrow a value for reading

drop(x);

dbg!(y);
dbg!(z);

Rule 0. Borrowing via references

  1. Rule 0. The owner should outlive all borrowers

Rule 1. Borrowing via references

  1. Rule 1. there can be many outstanding shared references
let x = String::from("Hello");
let y = &x; // borrow a value for reading
let z = &x; // borrow a value for reading

dbg!(z); //=> "Hello"
dbg!(y); //=> "Hello"
dbg!(x); //=> "Hello"

Rule 2. Borrowing via references

  1. Rule 2. There should be only one mutable reference
let mut x = String::from("Hello");
let y = &mut x; // borrow a value for writing
let z = &mut x; // borrow a value for writing
...

Rule 3. Borrowing via references

  1. Rule 3. Safety rule: {shared}+ xor mutable references
let mut x = String::from("Hello");
let y = &x;     // borrow a value for reading
let z = &mut x; // borrow a value for writing
...

Owned type: &str vs. String

let s = String::from("Hello"); // what's thye type of s?

let x = "Hello"; // what's the type of x?
let _: () = x;

Owned type: &str vs. String

let x = "Hello";

Owned type: &str vs. String

dbg!(std::mem::size_of::<str>());
//=> error: `str` doesn't have a size known at compile-time
dbg!(std::mem::size_of::<&str>());   //=> 16

dbg!(std::mem::size_of::<String>()); //=> 24
dbg!(std::mem::size_of::<&String>());//=>  8

Example: benefit of owned type

let mut x = String::from("Hello");
x.push_str(" World!"); // Isn't the size of String changed?

dbg!(x);

Example: benefit of owned type

impl String {
    pub fn push_str(&mut self, string: &str) { .. }
    //              |
    //              +--> a mutable reference
}

let mut x = String::from("Hello");

{
  //=> String::push_str(&x, " World!")
  x.push_str(" World!");
}

dbg!(x);

Introducing a slice type (e.g., &str)

let x: &str = "Hello";

Example of a slice type

let x = String::from("Hello World");
let y = &x[0..5];

Thinking of memory safety

// in C++
std::string s = "Hello";
const char *c = s.c_str();

s.append(" World!");

std::cout << c;
// in Rust
let mut s = String::from("Hello");
let c = s.as_str();
    
s.push_str(" World!");

dbg!(c);

Memory safety by restricted aliasing

let mut s = String::from("Hello");
let c = s.as_str();
    
s.push_str(" World!");

dbg!(c);

Next lecture

References