Rust Scopes and Ownership

Introduction

Scopes play an important part in ownership, borrowing, and lifetimes.

That is, they indicate to the compiler when borrows are valid, when resources can be freed, and when variables are created or destroyed.

What’s RAII

Rust enforces RAII, so whenever an object goes out of scope, its destructor is called and its owned resources are freed.

RAII: Resource Acquisition Is Initialization

This behavior shields against resource leak bugs, so you’ll never have to manually free memory or worry about memory leaks again!

Here’s a quick showcase:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 fn create_box() {
// Allocate an integer on the heap
let _box1 = Box::new(3i32);

// `_box1` is destroyed here, and memory gets freed
}

fn main() {
// Allocate an integer on the heap
let _box2 = Box::new(5i32);

// A nested scope:
{
// Allocate an integer on the heap
let _box3 = Box::new(4i32);

// `_box3` is destroyed here, and memory gets freed
}

// Creating lots of boxes just for fun
// There's no need to manually free memory!
for _ in 0u32..1_000 {
create_box();
}

// `_box2` is destroyed here, and memory gets freed
}

memory analyzer:rustc main.rs && valgrind ./main

Destructor

The notion of a destructor in Rust is provided through the Drop trait. The destructor is called when the resource goes out of scope.

This trait is not required to be implemented for every type, only implement it for your type if you require its own destructor logic.

showcase:

1
2
3
4
5
6
7
8
9
10
11
12
struct ToDrop;

impl Drop for ToDrop {
fn drop(&mut self) {
println!("ToDrop is being dropped");
}
}

fn main() {
let x = ToDrop;
println!("Made a ToDrop!");
}
1
2
Made a ToDrop!
ToDrop is being dropped

Ownership

Because variables are in charge of freeing their own resources, resources can only have one owner.

This also prevents resources from being freed more than once. Note that not all variables own resources (e.g. references).

When doing assignments (let x = y) or passing function arguments by value (foo(x)), the ownership of the resources is transferred. In Rust-speak, this is known as a move.

After moving resources, the previous owner can no longer be used. This avoids creating dangling pointers.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// This function takes ownership of the heap allocated memory
fn destroy_box(c: Box<i32>) {
println!("Destroying a box that contains {}", c);

// `c` is destroyed and the memory freed
}

fn main() {
// _Stack_ allocated integer
let x = 5u32;

// *Copy* `x` into `y` - no resources are moved
let y = x;

// Both values can be independently used
println!("x is {}, and y is {}", x, y);

// `a` is a pointer to a _heap_ allocated integer
let a = Box::new(5i32);

println!("a contains: {}", a);

// *Move* `a` into `b`
let b = a;
// The pointer address of `a` is copied (not the data) into `b`.
// Both are now pointers to the same heap allocated data, but
// `b` now owns it.

// Error! `a` can no longer access the data, because it no longer owns the
// heap memory
//println!("a contains: {}", a);
// TODO ^ Try uncommenting this line

// This function takes ownership of the heap allocated memory from `b`
destroy_box(b);

// Since the heap memory has been freed at this point, this action would
// result in dereferencing freed memory, but it's forbidden by the compiler
// Error! Same reason as the previous Error
//println!("b contains: {}", b);
// TODO ^ Try uncommenting this line
// error[E0382]: use of moved value: `a`
// println!("a contains: {}", a)
}

可以看到,不论是b,还是a,在调用destroy_box()后,都失去了ownership。

Borrowing

Most of the time, we’d like to access data without taking ownership over it. To accomplish this, Rust uses a borrowing mechanism. Instead of passing objects by value (T), objects can be passed by reference (&T).

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// This function takes ownership of a box and destroys it
fn eat_box_i32(boxed_i32: Box<i32>) {
println!("Destroying box that contains {} on eat_box_i32 function", boxed_i32);
}

// This function borrows an i32
fn borrow_i32(borrowed_i32: &i32) {
println!("This int is: {} on borrow_i32 function", borrowed_i32);
}

fn main() {
// Create a boxed i32, and a stacked i32
let boxed_i32 = Box::new(5_i32);
let stacked_i32 = 6_i32;

// Borrow the contents of the box. Ownership is not taken,
// so the contents can be borrowed again.
borrow_i32(&boxed_i32);
borrow_i32(&stacked_i32);

{
// Take a reference to the data contained inside the box
let _ref_to_i32: &i32 = &boxed_i32;

// Error!
// Can't destroy `boxed_i32` while the inner value is borrowed.
// eat_box_i32(boxed_i32);
// FIXME ^ Comment out this line
borrow_i32(&_ref_to_i32);
// `_ref_to_i32` goes out of scope and is no longer borrowed.
}

// `boxed_i32` can now give up ownership to `eat_box` and be destroyed
eat_box_i32(boxed_i32);
}

在block里面,调用eat_box_i32()尝试destroy boxed_i32前,boxed_i32已经被_ref_to_i32借用了,而且eat_box_i32()被包含在_ref_to_i32的scope里面,此时,eat_box_i32()试图destroy boxed_i32是不可能的事情。

Mutability

Mutable data can be mutably borrowed using &mut T. This is called a mutable reference and gives read/write access to the borrower.

In contrast, &T borrows the data via an immutable reference, and the borrower can read the data but not modify it:

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#[allow(dead_code)]
#[derive(Clone, Copy)]
struct Book {
// `&'static str` is a reference to a string allocated in read only memory
author: &'static str,
title: &'static str,
year: u32,
}

// This function takes a reference to a book
fn borrow_book(book: &Book) {
println!("I immutably borrowed {} - {} edition", book.title, book.year);
}

// This function takes a reference to a mutable book and changes `year` to 2014
fn new_edition(book: &mut Book) {
book.year = 2014;
println!("I mutably borrowed {} - {} edition", book.title, book.year);
}

fn main() {
// Create an immutable Book named `immutabook`
let immutabook = Book {
// string literals have type `&'static str`
author: "Douglas Hofstadter",
title: "Gödel, Escher, Bach",
year: 1979,
};

// Create a mutable copy of `immutabook` and call it `mutabook`
let mut mutabook = immutabook;

// Immutably borrow an immutable object
borrow_book(&immutabook);

// Immutably borrow a mutable object
borrow_book(&mutabook);

// Borrow a mutable object as mutable
new_edition(&mut mutabook);

// Error! Cannot borrow an immutable object as mutable
// new_edition(&mut immutabook);
// FIXME ^ Comment out this line
let mut modify_immute_book = immutabook;
new_edition(&mut modify_immute_book)
}

Freezing

When data is immutably borrowed, it also freezes. Frozen data can’t be modified via the original object until all references to it go out of scope:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fn main() {
let mut _mutable_integer = 7i32;

{
// Borrow `_mutable_integer`
let mut _large_integer = &_mutable_integer;

// Error! `_mutable_integer` is frozen in this scope
// cannot assign to `_mutable_integer` because it is borrowed
// _mutable_integer = 50;
// FIXME ^ Comment out this line
println!("before :{}", _large_integer);
_large_integer = &30i32;
// `_large_integer` goes out of scope
println!("after :{}", _large_integer)
}

// Ok! `_mutable_integer` is not frozen in this scope
_mutable_integer = 3;
}

Aliasing

Data can be immutably borrowed any number of times, but while immutably borrowed, the original data can’t be mutably borrowed. On the other hand, only one mutable borrow is allowed at a time. The original data can be borrowed again only after the mutable reference goes out of scope.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
struct Point { x: i32, y: i32, z: i32 }

fn main() {
let mut point = Point { x: 0, y: 0, z: 0 };

{
let borrowed_point = &point;
let another_borrow = &point;

// Data can be accessed via the references and the original owner
println!("Point has coordinates: ({}, {}, {})",
borrowed_point.x, another_borrow.y, point.z);

// Error! Can't borrow point as mutable because it's currently
// borrowed as immutable.
//let mutable_borrow = &mut point;
// TODO ^ Try uncommenting this line

// Immutable references go out of scope
}

{
let mutable_borrow = &mut point;

// Change data via mutable reference
mutable_borrow.x = 5;
mutable_borrow.y = 2;
mutable_borrow.z = 1;

// Error! Can't borrow `point` as immutable because it's currently
// borrowed as mutable.
//let y = &point.y;
// TODO ^ Try uncommenting this line

// Error! Can't print because `println!` takes an immutable reference.
//println!("Point Z coordinate is {}", point.z);
// TODO ^ Try uncommenting this line

// Ok! Mutable references can be passed as immutable to `println!`
println!("Point has coordinates: ({}, {}, {})",
mutable_borrow.x, mutable_borrow.y, mutable_borrow.z);

// Mutable reference goes out of scope
}

// Immutable references to point are allowed again
let borrowed_point = &point;
println!("Point now has coordinates: ({}, {}, {})",
borrowed_point.x, borrowed_point.y, borrowed_point.z);
}
  • 在第一个block里面,point已经被borrowed_point借用,此时如果试图进行mutable borrow是禁止的,但是immutable是可以多次的。即,如果一个对象已经被borrow了,都不能再进行mutable borrrow,除非borrow已经结束。

  • 在第二个block里面,point已经被mutable borrow,此时即使是immutable borrow也是不允许的。

The Ref Pattern

When doing pattern matching or destructuring via the let binding, the ref keyword can be used to take references to the fields of a struct/tuple.

The example below shows a few instances where this can be useful:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#[derive(Clone, Copy)]
struct Point { x: i32, y: i32 }

fn main() {
let c = 'Q';

// A `ref` borrow on the left side of an assignment is equivalent to
// an `&` borrow on the right side.
let ref ref_c1 = c;
let ref_c2 = &c;

println!("ref_c1 equals ref_c2: {}", *ref_c1 == *ref_c2);

let point = Point { x: 1, y: 0 };

// `ref` is also valid when destructuring a struct.
let _copy_of_x = {
// `ref_to_x` is a reference to the `x` field of `point`.
let Point { x: ref ref_to_x, y: _ } = point;

// Return a copy of the `x` field of `point`.
*ref_to_x
};
println!("copy_of_x: {}", _copy_of_x);

// A mutable copy of `point`
let mut mutable_point = point;
{
// `ref` can be paired with `mut` to take mutable references.
let Point { x: _, y: ref mut mut_ref_to_y } = mutable_point;

// Mutate the `y` field of `mutable_point` via a mutable reference.
*mut_ref_to_y = 3;
};

println!("point is ({}, {})", point.x, point.y);
println!("mutable_point is ({}, {})", mutable_point.x, mutable_point.y);

// A mutable tuple that includes a pointer
let mut mutable_tuple = (Box::new(5u32), 3u32);
println!("original tuple is {:?}", mutable_tuple);
{
// Destructure `mutable_tuple` to change the value of `last`.
let (_, ref mut last) = mutable_tuple;
*last = 2u32;
};

println!("tuple is {:?}", mutable_tuple);
}
1
2
3
4
5
6
ref_c1 equals ref_c2: true
copy_of_x: 1
point is (1, 0)
mutable_point is (1, 3)
original tuple is (5, 3)
tuple is (5, 2)

Refer

https://doc.rust-lang.org/stable/rust-by-example