loading...
ITNEXT

Rust: Ownership and Borrowing

abhirockzz profile image Abhishek Gupta Updated on ・3 min read

I am learning Rust and made some notes about Ownership and Borrowing concepts

note that I am still coming to grips with these concepts while "fighting with the Rust borrow checker" at the same time ;)

Ownership in Rust

Ownership = binding/association of value to a variable. The rules are:

  • Only one owner at a time
  • If the binding is "released" ownership is "gone" and data is "freed"
let p1 = Person::new(); //1
p2 = p1; //2

let p1 = Person::new();
do_something(p1); //out of scope

On line 2, p1 will be freed - it's out of scope since it has transferred ownership to p2. Passing p1 into another function also has the same effect

let p1 = Person::new();
p2 = p1;
println!("person {#:?}", p2)
println!("person {#:?}", p1) //compile error

That's the reason Rust does not allow you to use that value again. It enforces this at compile time using borrow checker

Drop

It is possible to implement the drop method from the Drop trait if custom cleanup logic is required. It will be automatically called by Rust when that value goes out of scope

Once a variable is out-of-scope i.e. its ownership has been relinquished, the Rust compiler will not allow you to use it again, since it needs to be "freed". This is also called Moving i.e. ownership of p1 moved to p2

Ownership is applicable to heap data only, not stack data since it (stack data) is "copied", not "moved"

Copy

The Copy trait allows a value to be copied. All the primitive types (e.g. i32, bool etc.) implement Copy implicitly. Custom types (e.g. structs) can implement Copy if all its components also implement Copy - to implement, you can either use #[derive(Copy)] or use an explicit implementation

Ways of passing ownership

... and keeping the compiler happy

  • use the clone() method
  • return from function
  • passing a reference
  • passing a mutable reference

Use clone()

Think of it as a deep copy like operation where it creates a separate copy of the data on the heap and points the new owner to it, leaving the old value untouched (or unmoved). Some types implement Clone trait by default (e.g. String), but you can mark your custom types as clone-able using #[derive(Clone)]

let p1 = Person::new();
let p2 = p1.clone();
do_something(p2);
println!("{:?}, p1); //works ok

Return from function

let p1 = Person::new();
do_something(p1);
...

fn do_something(p: Person) -> Person {
//logic
}

...

println!("p = {}",p1); //continue using p1 since ownership has been passed back

But this is just wired! You're forced to return something just because you want to keep the variable in scope

Passing a reference

let p1 = Person::new();
do_something(&p1);
println!("p = {}",p1); //continue using p1 since ownership has been was never passed on
...

fn do_something(p: &Person) {
//logic
}

...


The value of p1 did not move - this is called Borrowing. do_something never got the ownership, since all we did was pass a reference to p1 - notice the &

Mutable References

If you want do_something logic to update the name of the Person reference, you'll need to pass a mutable reference

let mut p1 = Person::new();
do_something(&mut p1);
println!("mutated info {}",p2)
...

fn do_something(p: &mut Person, new_name: String) {
   p.name = String::from(new_name);
}
...

Changes:

  • Variable p1 was declared as mut (mutable)
  • Passed &mut to do_something instead of just & - signifies mutable reference
  • do_something signature updated to &mut - signifies mutable reference

To dive in further, you can read up the following chapters from "The Rust Programming Language" book

Discussion

pic
Editor guide
Collapse
jhoobergs profile image
Jesse Hoobergs

Nice explanation.

I think you made a small typo 'Ownership is not applicable to heap data only, not stack data - they are "copied", not "moved"'

The first not is not wrong?

Collapse
abhirockzz profile image
Abhishek Gupta Author

Thanks a lot for pointing that out :) Fixed! And glad you like it