CS代考 # Heap allocation and Smart Pointers

# Heap allocation and Smart Pointers

Source: [The Rust Book](https://doc.rust-lang.org/book/ch15-00-smart-pointers.html)

Copyright By PowCoder代写 加微信 powcoder

The most straightforward smart pointer is a box. Boxes allow you to store data on the heap rather than the stack. What remains on the stack is the pointer to the heap data. You’ll use them most often in these situations:

– When you have a type whose size can’t be known at compile time and you want to use a value of that type in a context that requires an exact size
– When you have a large amount of data and you want to transfer ownership but ensure the data won’t be copied when you do so
– When you want to own a value and you care only that it’s a type that implements a particular trait rather than being of a specific type

### Using a Box to Store Data on the Heap

fn main() {
let b = Box::new(5);
println!(“b = {}”, b);

We define the variable b to have the value of a Box that points to the value 5, which is allocated on the heap. This program will print b = 5; in this case, we can access the data in the box similar to how we would if this data were on the stack. Just like any owned value, when a box goes out of scope, as b does at the end of main, it will be deallocated. The deallocation happens for the box (stored on the stack) and the data it points to (stored on the heap).

Boxed values can be dereferenced using the `*` operator, which removes one layer of indirection. Below we have a more complicated example, which we will talk through line by line.

use std::mem;

#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
struct Point {

// A Rectangle can be specified by where its top left and bottom right
// corners are in space
#[allow(dead_code)]
struct Rectangle {
top_left: Point,
bottom_right: Point,

fn origin() -> Point {
Point { x: 0.0, y: 0.0 }

fn boxed_origin() -> Box {
// Allocate this point on the heap, and return a pointer to it
Box::new(Point { x: 0.0, y: 0.0 })

fn main() {
// (all the type annotations are superfluous)
// Stack allocated variables
let point: Point = origin();
let rectangle: Rectangle = Rectangle {
top_left: origin(),
bottom_right: Point { x: 3.0, y: -4.0 }

// Heap allocated rectangle
let boxed_rectangle: Box = Box::new(Rectangle {
top_left: origin(),
bottom_right: Point { x: 3.0, y: -4.0 },

// The output of functions can be boxed
let boxed_point: Box = Box::new(origin());

// Double indirection
let box_in_a_box: Box> = Box::new(boxed_origin());

println!(“Point occupies {} bytes on the stack”,
mem::size_of_val(&point));
println!(“Rectangle occupies {} bytes on the stack”,
mem::size_of_val(&rectangle));

// box size == pointer size
println!(“Boxed point occupies {} bytes on the stack”,
mem::size_of_val(&boxed_point));
println!(“Boxed rectangle occupies {} bytes on the stack”,
mem::size_of_val(&boxed_rectangle));
println!(“Boxed box occupies {} bytes on the stack”,
mem::size_of_val(&box_in_a_box));

// Copy the data contained in `boxed_point` into `unboxed_point`
let unboxed_point: Point = *boxed_point;
println!(“Unboxed point occupies {} bytes on the stack”,
mem::size_of_val(&unboxed_point));

### Recursive Types with Box

At compile time, Rust needs to know how much space a type takes up. One type whose size can’t be known at compile time is a recursive type, where a value can have as part of itself another value of the same type. Because this nesting of values could theoretically continue infinitely, Rust doesn’t know how much space a value of a recursive type needs. However, boxes have a known size, so by inserting a box in a recursive type definition, you can have recursive types.

We can implement this using a Cons list. A cons list is a data structure that comes from the Lisp programming language and its dialects. In Lisp, the cons function (short for “construct function”) constructs a new pair from its two arguments, which usually are a single value and another pair. These pairs containing pairs form a list.

The cons function concept has made its way into more general functional programming jargon: “to cons x onto y” informally means to construct a new container instance by putting the element x at the start of this new container, followed by the container y.

Each item in a cons list contains two elements: the value of the current item and the next item. The last item in the list contains only a value called Nil without a next item. A cons list is produced by recursively calling the cons function. The canonical name to denote the base case of the recursion is Nil.

To implement this data structure, we can first define the following enum.

enum List {
Cons(i32, List),

Then we can create our list as shown below.

use crate::List::{Cons, Nil};

fn main() {
let list = Cons(1, Cons(2, Cons(3, Nil)));

When compiling this, we get an error. Why? Rust can’t figure out how much space to allocate for recursively defined types.

enum List {
Cons(i32, Box),

use crate::List::{Cons, Nil};

fn main() {
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));

Boxes provide only the indirection and heap allocation; they don’t have any other special capabilities, like those we’ll see with the other smart pointer types. They also don’t have any performance overhead that these special capabilities incur, so they can be useful in cases like the cons list where the indirection is the only feature we need.

## Smart Pointers

A pointer is a general concept for a variable that contains an address in memory. This address refers to, or “points at,” some other data. The most common kind of pointer in Rust is a reference, which you learned about in previous lectures. References are indicated by the & symbol and borrow the value they point to. They don’t have any special capabilities other than referring to data. Also, they don’t have any overhead and are the kind of pointer we use most often.

Smart pointers, on the other hand, are data structures that not only act like a pointer but also have additional metadata and capabilities. The concept of smart pointers isn’t unique to Rust: smart pointers originated in C++ and exist in other languages as well. In Rust, the different smart pointers defined in the standard library provide functionality beyond that provided by references. One example that we’ll explore in this chapter is the reference counting smart pointer type. This pointer enables you to have multiple owners of data by keeping track of the number of owners and, when no owners remain, cleaning up the data.

### The Deref trait

Implementing the Deref trait allows you to customize the behavior of the dereference operator, * (as opposed to the multiplication). By implementing Deref in such a way that a smart pointer can be treated like a regular reference, you can write code that operates on references and use that code with smart pointers too.

Here is a simple example of using Box like a reference.

fn main() {
let x = 5;
let y = Box::new(x);

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

The trait that enables this behavior is known as the `Deref` trait. We can see how it works with the following example.

struct MyBox(T);

impl MyBox {
fn new(x: T) -> MyBox {

Here we have a basic struct MyBox, for which the new method creates a new box with an element of type T. We can allow this type to be dereferenced by implementing the `Deref` trait.

use std::ops::Deref;

impl Deref for MyBox {
type Target = T;

fn deref(&self) -> &Self::Target {

This would allow us to compile the following example.

fn main() {
let x = 5;
let y = MyBox::new(x);

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

Behind the scenes in this example, what is actually run is this.

*(y.deref())

Without the Deref trait, the compiler can only dereference & references. The deref method gives the compiler the ability to take a value of any type that implements Deref and call the deref method to get a & reference that it knows how to dereference. The reason the deref method returns a reference to a value, and that the plain dereference outside the parentheses in \*(y.deref()) is still necessary, is the ownership system. If the deref method returned the value directly instead of a reference to the value, the value would be moved out of self.

### The Drop trait

The second trait important to the smart pointer pattern is Drop, which lets you customize what happens when a value is about to go out of scope. You can provide an implementation for the Drop trait on any type, and the code you specify can be used to release resources like files or network connections. We’re introducing Drop in the context of smart pointers because the functionality of the Drop trait is almost always used when implementing a smart pointer. For example, when a Box is dropped it will deallocate the space on the heap that the box points to.

The following examples shows implementing a custom smart pointer, which will print when the instance goes out of scope.

struct CustomSmartPointer {
data: String,

impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!(” SmartPointer with data `{}`!”, self.data);

fn main() {
let c = CustomSmartPointer {
data: String::from(“my stuff”),
let d = CustomSmartPointer {
data: String::from(“other stuff”),
println!(“End of main”);

When running this example, Rust automatically called drop for us when our instances went out of scope, calling the code we specified. Variables are dropped in the reverse order of their creation, so d was dropped before c. This example gives you a visual guide to how the drop method works; usually you would specify the cleanup code that your type needs to run rather than a print message.

You can also call drop manually.

struct CustomSmartPointer {
data: String,

impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!(” SmartPointer with data `{}`!”, self.data);

fn main() {
let c = CustomSmartPointer {
data: String::from(“my stuff”),
let d = CustomSmartPointer {
data: String::from(“other stuff”),
println!(“End of main”);

In this case c is dropped before d, and before the variable goes out of scope.

## Reference counted smart pointers

Given that background on how smart pointers are implemented, we can now discuss Rc, the reference counted smart pointer. In the majority of cases, ownership is clear: you know exactly which variable owns a given value. However, there are cases when a single value might have multiple owners. For example, in graph data structures, multiple edges might point to the same node, and that node is conceptually owned by all of the edges that point to it. A node shouldn’t be cleaned up unless it doesn’t have any edges pointing to it.

To enable multiple ownership, Rust has a type called Rc, which is an abbreviation for reference counting. The Rc type keeps track of the number of references to a value to determine whether or not the value is still in use. If there are zero references to a value, the value can be cleaned up without any references becoming invalid.

enum List {
Cons(i32, Box),

use crate::List::{Cons, Nil};

fn main() {
let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
let b = Cons(3, Box::new(a));
let c = Cons(4, Box::new(a));

Here is an example that attempts multiple ownership. Will this compile? Why or why not?

To fix this example, we can change the definition of List to uce Rc in place of Box. When we create b, instead of taking ownership of a, we’ll clone the Rc that a is holding, thereby increasing the number of references from one to two and letting a and b share ownership of the data in that Rc. We’ll also clone a when creating c, increasing the number of references from two to three. Every time we call Rc::clone, the reference count to the data within the Rc will increase, and the data won’t be cleaned up unless there are zero references to it.

enum List {
Cons(i32, Rc),

use crate::List::{Cons, Nil};
use std::rc::Rc;

fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
let b = Cons(3, Rc::clone(&a));
let c = Cons(4, Rc::clone(&a));

We need to add the `use` statement to bring `Rc` into scope. The implementation of Rc::clone doesn’t make a deep copy of all the data like most types’ implementations of clone do. The call to Rc::clone only increments the reference count, which doesn’t take much time.

In the following example, we can see the reference count values after each assigment statement.

fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!(“count after creating a = {}”, Rc::strong_count(&a));
let b = Cons(3, Rc::clone(&a));
println!(“count after creating b = {}”, Rc::strong_count(&a));
let c = Cons(4, Rc::clone(&a));
println!(“count after creating c = {}”, Rc::strong_count(&a));
println!(“count after c goes out of scope = {}”, Rc::strong_count(&a));

### Implicit Deref coercion is a convenience that Rust performs on arguments to functions and methods. Deref coercion works only on types that implement the Deref trait. Deref coercion converts such a type into a reference to another type. For example, deref coercion can convert &String to &str because String implements the Deref trait such that it returns &str. Deref coercion happens automatically when we pass a reference to a particular type’s value as an argument to a function or method that doesn’t match the parameter type in the function or method definition. A sequence of calls to the deref method converts the type we provided into the type the parameter needs.

Deref coercion was added to Rust so that programmers writing function and method calls don’t need to add as many explicit references and dereferences with & and *. The deref coercion feature also lets us write more code that can work for either references or smart pointers.

To see an example, we can sue our MyBox type.

fn main() {
let m = MyBox::new(String::from(“Rust”));
hello(&m);

Here we’re calling the hello function with the argument &m, which is a reference to a MyBox value. Because we implemented the Deref trait on MyBox, Rust can turn &MyBox into &String by calling deref. The standard library provides an implementation of Deref on String that returns a string slice, and this is in the API documentation for Deref. Rust calls deref again to turn the &String into &str, which matches the hello function’s definition.

If Rust didn’t implement deref coercion, we would have to write the following code.

fn main() {
let m = MyBox::new(String::from(“Rust”));
hello(&(*m)[..]);

Similar to how you use the Deref trait to override the * operator on immutable references, you can use the DerefMut trait to override the * operator on mutable references. Rust does deref coercion when it finds types and trait implementations in three cases:

– From `&T` to `&U` when `T: Deref`
– From `&mut T` to `&mut U` when `T: DerefMut`
– From `&mut T` to `&U` when `T: Deref`

The first two cases are the same except for mutability. The first case states that if you have a &T, and T implements Deref to some type U, you can get a &U transparently. The second case states that the same deref coercion happens for mutable references.

The third case is trickier: Rust will also coerce a mutable reference to an immutable one. But the reverse is not possible: immutable references will never coerce to mutable references. Because of the borrowing rules, if you have a mutable reference, that mutable reference must be the only reference to that data (otherwise, the program wouldn’t compile). Converting one mutable reference to one immutable reference will never break the borrowing rules. Converting an immutable reference to a mutable reference would require that the initial immutable reference is the only immutable reference to that data, but the borrowing rules don’t guarantee that. Therefore, Rust can’t make the assumption that converting an immutable reference to a mutable reference is possible.

Similar to Rc, Arc (atomic reference counted) can be used when sharing data across threads. This struct, via the Clone implementation can create a reference pointer for the location of a value in the memory heap while increasing the reference counter. As it shares ownership between threads, when the last reference pointer to a value is out of scope, the variable is dropped.

fn main() {
use std::sync::Arc;
use std::thread;

// This variable declaration is where its value is specified.
let apple = Arc::new(“the same apple”);

for _ in 0..10 {
// Here there is no value specification as it is a pointer to a reference
// in the memory heap.
let apple = Arc::clone(&apple);

thread::spawn(move || {
// As Arc was used, threads can be spawned using the value allocated
// in the Arc variable pointer’s location.
println!(“{:?}”, apple);

程序代写 CS代考 加微信: powcoder QQ: 1823890830 Email: powcoder@163.com