Rust Programming

Object Ownership and Moving

Memory un-safety and bugs usually come from dynamically allocated objects on the heap that are not successfully reclaimed (memory leak), or is freed more than once.

The solution that Rust has come up with is the idea of ownership, where you can’t have multiple variables reference the same heap objects simultaneously

This trivial example shows how a heap object (or value) can be moved from one variable to another

fn main(){
    //allocates a dynamic, mutable string on the heap
    let mut s1 = String::new();

    //here s1 can be used and modified

    // object (value) to be moved from s1 to s2
    let mut s2 = s1;

    //here s1 is no longer a valid reference to the object

    //here s2 goes out of scope, and heap memory reclaimed
}

This less trivial example shows how ownership can be moved into an out of function calls

fn main(){
    //allocate object on the heap
    let s1 = String::new();

    //declare an uninitialized String variable
    let s2: String;

    //moving ownership to the function, then function moves ownership back to variable s2
    s2 = add_data(s1);

    //variable s1 no longer a valid reference to the object

    //s2 goes out of scope, and memory gets reclaimed
}

fn add_data(st: String) -> String {
    st.push_str("Some Data");
    return st;
}

Object Borrowing and References

Passing a reference to an object doesn’t move ownership. There are two kinds of reference passing

  • Passing an immutable reference

  • Passing a mutable reference

Here is an example of both

fn main() {
    //heap object
    let mut s1 = String::new();

    borrow_and_modify(&mut s1);
    //here s1 is still valid, as ownership was never moved, only borrowed

    borrows_object(&s1);

} // here s1 goes out of scope, and thus destroyed and reclaimed

// declared to borrow an object that would be mutated
fn borrow_and_modify(st: &mut String){
    st.push_str("Adding some Data");
}

// declared to borrow an object that would not be mutated in anyway
fn borrows_object(st: &String){
    println!("sring length is {}",st.len());
}

Notice where the mut keyword needs to be used

  1. In variable declaration, to indicate that the object is mutable

  2. In the declaration of a borrowing function params that would be mutated within the function

  3. When passing a reference to an object to a borrowing function that intends to mutate the object

Primative Borrowing and dereferencing

Since primative types are generally stored on the stack, and can easily and cheaply be copied, they need to be dereferenced when being passed and mutated within functions

fn main() {
    let mut x: u8 = 0; //needs to be declared mutable
    increment(&mut x); //pass a mutable reference to a borrowing function
    println!("current value: {}",x);
}

//defines a borrowing function that mutates a primitive
fn increment(d: &mut u8){
    *d = *d + 1; //dereferencing is required here
}