1. Stack/heap allocations and boxing 2. Traits
3. Generics
Copyright By PowCoder代写 加微信 powcoder
4. From/Into
5. Dereferencing
6. Reference counting
7. Destructors
8. Traits and bounds
9. Option/Result composition
10. Iterators
To explain things you may have used in HW 3 without fully understanding
To better prepare you for HW 5
Stack and Heap Allocation
How to allocate something on the stack?
Stack and Heap Allocation
How to allocate something on the stack? Just make local variable!
Stack and Heap Allocation
How to allocate something on the stack? Just make local variable!
How to allocate something on the heap?
Stack and Heap Allocation
How to allocate something on the stack? Just make local variable!
How to allocate something on the heap?
Can’t call malloc. Not allowed in safe Rust (the type of Rust you’ve been learning).
Heap Allocation
Method 1: use unsafe Rust!
let a = 10;
let ptr = alloc(a); }
Heap Allocation
Method 1: use unsafe Rust!
Probably not a good idea to write too much unsafe Rust code. With unsafe Rust, lots of our compiler guarantees are gone, so it is easy to make mistakes.
let a = 10;
let ptr = alloc(a); }
Heap Allocation
Method 1: use unsafe Rust!
Probably not a good idea to write too much unsafe Rust code. With unsafe Rust, lots of our compiler guarantees are gone, so it is easy to make mistakes.
(HW 5 will not allow unsafe code.)
let a = 10;
let ptr = alloc(a); }
Heap Allocation
Method 2: use Box (or Arc/Rc)
let a = Box::new(10);
assert_eq!(*a, 10); // use * to get the inner value
Heap Allocation
Method 2: use Box (or Arc/Rc)
Box uses some unsafe code to allocate space but wraps everything under a safe interface.
let a = Box::new(10);
assert_eq!(*a, 10); // use * to get the inner value
(Aside: any library function that allocates data on the heap, like Vec and String, must run some unsafe code.)
Traits define shared behavior. They are similar to interfaces in other languages.
Define a trait:
trait Shape {
fn area(&self) -> i64;
fn perimeter(&mut self) -> i64;
Implement a trait on a type:
struct Rect { x0: i64,
y0: i64, x1: i64, y1: i64,
// Assumes that x1 >= x0 and y1 >= y0
impl Shape for Rect {
fn area(&self) -> i64 {
(self.x1 – self.x0) * (self.y1 – self.y0) }
fn perimeter(&mut self) -> i64 {
2 * (self.x1 – self.x0 + self.y1 – self.y0)
We can clean up the previous code a bit:
impl Rect {
fn width(&self) -> i64 {
self.x1 – self.x0 }
fn height(&self) -> i64 { self.y1 – self.y0
impl Shape for Rect {
fn area(&self) -> i64 {
self.width() * self.height() }
fn perimeter(&mut self) -> i64 {
2 * (self.width() + self.height())
Traits are very useful in combination with generics and boxing, which we’ll discuss later.
Like many other languages, Rust has generics. Using generics lets you write code that works for many types.
Like many other languages, Rust has generics. Using generics lets you write code that works for many types.
We’ll first show you the syntax for generics, and then we’ll describe how to use them in practice.
Like many other languages, Rust has generics. Using generics lets you write code that works for many types.
We’ll first show you the syntax for generics, and then we’ll describe how to use them in practice.
Generic functions look like this:
fn example1
fn example2
Generic structs look like this:
struct Rect
// The generic type parameter T is inferred.
let r = Rect { x0: 1, y0: 2, x1: 3, y1: 5 };
let r = Rect { x0: 1.5, y0: 2.5, x1: 3.5, y1: 5.5 };
Generic structs look like this:
struct Rect
// The generic type parameter T is inferred.
let r = Rect { x0: 1, y0: 2, x1: 3, y1: 5 };
let r = Rect { x0: 1.5, y0: 2.5, x1: 3.5, y1: 5.5 };
This won’t work:
let r = Rect { x0: 1.5, y0: 2, x1: 3, y1: 5 };
The problem is that all the fields of Rect must have the same type T. But here, were trying to use a float for x0 and integers for the other fields.
If you want to explicitly specify the type parameter(s), use turbofish syntax:
let r = Rect::
If you want to explicitly specify the type parameter(s), use turbofish syntax:
let r = Rect::
Why “turbofish”? Because ::<_> sort of looks like a fish.
If you want to explicitly specify the type parameter(s), use turbofish syntax:
let r = Rect::
Why “turbofish”? Because ::<_> sort of looks like a fish. Alternatively, add a type annotation:
let r: Rect
You can have multiple generic parameters. Here’s an example:
struct Rect
You can have multiple generic parameters. Here’s an example:
struct Rect
Now you can use integers for the x-coordinates and floats for the y- coordinates:
let r = Rect { x0: 1, x1: 5, y0: 0.12, y1: 1.64 };
impl blocks for generic types look like this:
impl
fn copy(&mut self, other: Rect
self.x0 = other.x0; self.x1 = other.x1; self.y0 = other.y0; self.y1 = other.y1;
fn transpose(self) -> Rect
Rect { x0: self.y0, x1: self.y1, y0: self.x0, y1: self.x1 }
impl blocks for generic types look like this:
impl
fn copy(&mut self, other: Rect
self.x0 = other.x0; self.x1 = other.x1; self.y0 = other.y0; self.y1 = other.y1;
fn transpose(self) -> Rect
Rect { x0: self.y0, x1: self.y1, y0: self.x0, y1: self.x1 }
Functions in impl blocks can have their own, distinct generic types:
impl
fn set_x
Rect { x0, x1, y0: self.y0, y1: self.y1 } }
You can also have generic enums!
You’ve seen this already, in the form of Option and Result:
pub enum Option
Some(T), }
pub enum Result
You can also have generic enums!
You’ve seen this already, in the form of Option and Result:
pub enum Option
Some(T), }
pub enum Result
Traits can also have generics, as we’ll see next.
From and Into are generic traits. They are useful when you want to convert between types.
From and Into are generic traits. They are useful when you want to convert between types.
This is the definition of the From trait:
pub trait From
fn from(T) -> Self;
From and Into are generic traits. They are useful when you want to convert between types.
This is the definition of the From trait:
This is the definition of Into:
pub trait From
fn from(T) -> Self;
pub trait Into
fn into(self) -> T;
Let’s look at an example.
enum Apple { Gala,
enum Fruit { Apple(Apple), // …
Let’s look at an example.
enum Apple { Gala,
enum Fruit { Apple(Apple), // …
It should be easy to convert an Apple into a Fruit. (But not the other way around).
Let’s look at an example.
enum Apple { Gala,
enum Fruit { Apple(Apple), // …
It should be easy to convert an Apple into a Fruit. (But not the other way around).
Here’s how you might do that:
impl Fruit {
fn from_apple(apple: Apple) -> Self {
Self::Apple(apple)
But the more idiomatic way to do this in Rust is to use the From trait:
impl From
fn from(apple: Apple) -> Self {
Self::Apple(apple)
This makes it clear to people reading/using your code that an Apple can be converted into a Fruit.
But the more idiomatic way to do this in Rust is to use the From trait:
impl From
fn from(apple: Apple) -> Self {
Self::Apple(apple)
This makes it clear to people reading/using your code that an Apple can be converted into a Fruit.
The Rust standard library also implements the Into trait for you. Specifically, it implements Into
let my_apple = Apple::Gala;
let my_fruit: Fruit = my_apple.into();
yllacitamotua
But the more idiomatic way to do this in Rust is to use the From trait:
impl From
fn from(apple: Apple) -> Self {
Self::Apple(apple)
This makes it clear to people reading/using your code that an Apple can be converted into a Fruit.
The Rust standard library also implements the Into trait for you. Specifically, it implements Into
Note that the compiler usually won’t be able to figure out what type you want to convert into, so you may need to add type annotations, as we did for my_fruit.
let my_apple = Apple::Gala;
let my_fruit: Fruit = my_apple.into();
yllacitamotua
The Deref Trait
Many types in Rust act like “smart pointers”.
Like a regular pointer, they can be dereferenced via * (the dereference operator), but they also have some extra logic.
The Deref Trait
Many types in Rust act like “smart pointers”.
Like a regular pointer, they can be dereferenced via * (the dereference operator), but they also have some extra logic.
For example, an Arc
The Deref Trait
Many types in Rust act like “smart pointers”.
Like a regular pointer, they can be dereferenced via * (the dereference operator), but they also have some extra logic.
For example, an Arc
The “pointer-like” behavior is usually provided by implementing the Deref trait.
The Deref Trait
This is the definition of the std::ops::Deref trait:
pub trait Deref { type Target: ?Sized;
fn deref(&self) -> &Self::Target; }
The Deref Trait
This is the definition of the std::ops::Deref trait:
pub trait Deref { type Target: ?Sized;
fn deref(&self) -> &Self::Target; }
Target is an associated type of the Deref trait.
The Deref Trait
This is the definition of the std::ops::Deref trait:
pub trait Deref { type Target: ?Sized;
fn deref(&self) -> &Self::Target; }
Target is an associated type of the Deref trait.
Types that implement Deref must be able to provide a reference to something of type Target.
The Deref Trait
This is the definition of the std::ops::Deref trait:
pub trait Deref { type Target: ?Sized;
fn deref(&self) -> &Self::Target; }
Target is an associated type of the Deref trait.
Types that implement Deref must be able to provide a reference to something of type Target.
Don’t worry too much about ?Sized – it just means that Target need not have a size known at compile-time.
The Deref Trait
This is the definition of the std::ops::Deref trait:
pub trait Deref { type Target: ?Sized;
fn deref(&self) -> &Self::Target; }
Target is an associated type of the Deref trait.
Types that implement Deref must be able to provide a reference to something of type Target.
Don’t worry too much about ?Sized – it just means that Target need not have a size known at compile-time.
Associated types are just a different way of writing generic code; Deref could conceivably been written with a generic parameter, eg. Deref
The Deref Trait
The Rust compiler only knows how to dereference &T and &mut T.
If ptr is some other type that implements Deref, then *ptr gets implicitly converted to *Deref::deref(&ptr). The deref() method returns an &Target, which the compiler know how to dereference.
The Deref Trait
The Rust compiler only knows how to dereference &T and &mut T.
If ptr is some other type that implements Deref, then *ptr gets implicitly converted to *Deref::deref(&ptr). The deref() method returns an &Target, which the compiler know how to dereference.
The rust compiler can insert repeated calls to deref to try to coerce one type into another. This is known as deref coercion.
The Deref Trait
The Rust compiler only knows how to dereference &T and &mut T.
If ptr is some other type that implements Deref, then *ptr gets implicitly converted to *Deref::deref(&ptr). The deref() method returns an &Target, which the compiler know how to dereference.
The rust compiler can insert repeated calls to deref to try to coerce one type into another. This is known as deref coercion.
For example, an &Box
1. Dereference the Box to get an &String. 2. Dereference the String to get an &str.
The Deref Trait
The Rust compiler only knows how to dereference &T and &mut T.
If ptr is some other type that implements Deref, then *ptr gets implicitly converted to *Deref::deref(&ptr). The deref() method returns an &Target, which the compiler know how to dereference.
The rust compiler can insert repeated calls to deref to try to coerce one type into another. This is known as deref coercion.
For example, an &Box
2. Dereference the String to get an &str.
This is why helper methods generally take in an &str rather than a String! You
can convert a String to &str inexpensively (but not the other way around).
This is the definition of the DerefMut trait:
pub trait DerefMut: Deref {
fn deref_mut(&mut self) -> &mut Self::Target;
This is the definition of the DerefMut trait:
DerefMut is a subtrait of Deref, which means anything that implements DerefMut must also implement Deref.
(This is about the closest you’ll get to inheritance in Rust.)
pub trait DerefMut: Deref {
fn deref_mut(&mut self) -> &mut Self::Target;
This is the definition of the DerefMut trait:
DerefMut is a subtrait of Deref, which means anything that implements DerefMut must also implement Deref.
(This is about the closest you’ll get to inheritance in Rust.)
Everything we said about Deref also applies to DerefMut; the only difference is that you get a mutable reference instead of an immutable one.
pub trait DerefMut: Deref {
fn deref_mut(&mut self) -> &mut Self::Target;
This is the definition of the DerefMut trait:
DerefMut is a subtrait of Deref, which means anything that implements DerefMut must also implement Deref.
(This is about the closest you’ll get to inheritance in Rust.)
Everything we said about Deref also applies to DerefMut; the only difference is that you get a mutable reference instead of an immutable one.
So if ptr is a value of a type that implements DerefMut, *ptr = … is implicitly *DerefMut::deref_mut(&ptr) = ….
pub trait DerefMut: Deref {
fn deref_mut(&mut self) -> &mut Self::Target;
Method Resolution
Rust will automatically deref things for you when resolving methods.
This is why you can call .lock() on a value of type Arc
Method Resolution
Rust will automatically deref things for you when resolving methods.
This is why you can call .lock() on a value of type Arc
dereferenced to get a Mutex.
The precise rules for method resolution are somewhat complex; you can read about them here.
Reference Counting
An Arc
Reference Counting
An Arc
An Rc
Reference Counting
An Arc
An Rc
An Arc allows you to share data between threads while guaranteeing that the inner data is not freed until all threads are done accessing it.
Reference Counting
An Arc
An Rc
An Arc allows you to share data between threads while guaranteeing that the inner data is not freed until all threads are done accessing it.
Arcs are cheaply cloneable. Calling Arc::clone(&arc) only increments a reference count; it does not copy the underlying data. You can then send the new Arc to another thread.
Destructors via Drop
Remember that “dropping” in Rust is somewhat analogous to “freeing” in C.
The std::ops::Drop trait allows you to run some code when a value goes out of scope or is no longer in use.
Destructors via Drop
Remember that “dropping” in Rust is somewhat analogous to “freeing” in C.
The std::ops::Drop trait allows you to run some code when a value goes out of scope or is no longer in use.
This is typically used if you need to implement custom logic for cleaning up resources.
Destructors via Drop
Remember that “dropping” in Rust is somewhat analogous to “freeing” in C.
The std::ops::Drop trait allows you to run some code when a value goes out of scope or is no longer in use.
This is typically used if you need to implement custom logic for cleaning up resources.
This is the definition of the Drop trait:
pub trait Drop {
fn drop(&mut self);
Destructors via Drop
Remember that “dropping” in Rust is somewhat analogous to “freeing” in C.
The std::ops::Drop trait allows you to run some code when a value goes out of scope or is no longer in use.
This is typically used if you need to implement custom logic for cleaning up resources.
This is the definition of the Drop trait:
Occasional annoyance: drop is not async.
pub trait Drop {
fn drop(&mut self);
Destructors via Drop
Note that drop takes in &mut self, not self.
Destructors via Drop
Note that drop takes in &mut self, not self. You might think you could do this:
That could be a double-free error!
let mut x = …; x.drop(); x.drop();
Destructors via Drop
Note that drop takes in &mut self, not self. You might think you could do this:
That could be a double-free error!
To prevent you from misusing Drop, you cannot call drop manually.
You can only call std::mem::drop, which takes ownership of the value being dropped and then calls std::ops::Drop::drop on it.
let mut x = …; x.drop(); x.drop();
Destructors via Drop
Things a destructor might do:
Freeing memory
Closing a file
Decrementing a reference count Killing a child process
After a destructor is run, Rust will recursively call drop on all struct/enum fields.
Drop Example
Suppose you want to for
程序代写 CS代考 加微信: powcoder QQ: 1823890830 Email: powcoder@163.com