Estimated time: 1 day
Box
is a pointer that owns heap-allocated data. This is the most common and simples form of heap allocation in Rust.
It's more idiomatic to use references (&T
/&mut T
) for pointing to the data, however they often come with lifetimes complexity. Box
allows to avoid this complexity at the cost of heap allocation.
Box
is also a way to go if an owned slice is needed, but is not intended to be resized. For example, Box<str>
/Box<[T]>
are often used instead String
/Vec<T>
in such cases.
For better understanding Box
purpose, design, limitations and use cases read through:
- Rust Book: 15.1. Using Box to Point to Data on the Heap
- Official
std::boxed
docs - Amos: What's in the box?
- Mahdi Dibaiee: What is
Box<str>
and how is it different fromString
in Rust?
It is sometimes useful to have objects that are guaranteed to not move, in the sense that their placement in memory does not change, and can thus be relied upon. A prime example of such a scenario would be building self-referential structs, since moving an object with pointers to itself will invalidate them, which could cause undefined behavior.
Pin<P>
ensures that the pointee of any pointer type P
has a stable location in memory, meaning it cannot be moved elsewhere and its memory cannot be deallocated until it gets dropped. We say that the pointee is "pinned".
However, many types are always freely movable, even when pinned, because they do not rely on having a stable address. This includes all the basic types (like bool
, i32
, references) as well as types consisting solely of these types. Types that do not care about pinning implement the Unpin
marker trait, which cancels the effect of Pin
. For T: Unpin
, Pin<Box<T>>
and Box<T>
function identically, as do Pin<&mut T>
and &mut T
.
Note, that pinning and Unpin
only affect the pointed-to type P::Target
, not the pointer type P
itself that got wrapped in Pin<P>
. For example, whether or not Box<T>
is Unpin
has no effect on the behavior of Pin<Box<T>>
(here, T
is the pointed-to type).
For better understanding Pin
purpose, design, limitations and use cases read through:
- Official
std::pin
docs - Reddit: Pinned objects ELI5?
- SoByte: Pin and Unpin in Rust
- Adam Chalmers: Pin, Unpin, and why Rust needs them
- Tamme Schichler: Pinning in plain English
- Yoshua Wuyts: Safe Pin Projections Through View Types
- Official
#[pin_project]
docs - Alice Ryhl answers on "Pin tutorial are confusing me"
- Rust Forum: Why is it unsafe to pin a shared reference?
- Ohad Ravid: Put a Pin on That
- Razieh Behjati: Leaky Abstractions and a Rusty Pin
- Saoirse Shipwreckt: Pin
-
For the following types:
Box<T>
,Rc<T>
,Vec<T>
,String
,&[u8]
,T
.
Implement the following traits:trait SayHi: fmt::Debug { fn say_hi(self: Pin<&Self>) { println!("Hi from {:?}", self) } }
trait MutMeSomehow { fn mut_me_somehow(self: Pin<&mut Self>) { // Implementation must be meaningful, and // obviously call something requiring `&mut self`. // The point here is to practice dealing with // `Pin<&mut Self>` -> `&mut self` conversion // in different contexts, without introducing // any `Unpin` trait bounds. } }
-
For the following structure:
struct MeasurableFuture<Fut> { inner_future: Fut, started_at: Option<std::time::Instant>, }
Provide a
Future
trait implementation, transparently polling theinner_future
, and printing its execution time in nanoseconds once it's ready. UsingFut: Unpin
trait bound (or similar) is not allowed.
After completing everything above, you should be able to answer (and understand why) the following questions:
- What does "boxing" mean in Rust? How is it useful? When and why is it required?
- What is
Pin
and why is it required? What guarantees does it provide? How does it fulfill them? - How does
Unpin
affect thePin
? What does it mean? - Is it allowed to move pinned data after the
Pin
dies? Why? - What is structural pinning? When it should be used and why?
- What is
Pin
projection? Why does it exist? How is it used?