src/unsafe-deep-dive/pinning/self-referential-buffer/rust-pin.md
Pin<Ptr>Pinning allows Rust programmers to create a type which is much more similar to C++ classes.
# // Copyright 2026 Google LLC
# // SPDX-License-Identifier: Apache-2.0
#
use std::marker::PhantomPinned;
use std::pin::Pin;
/// A self-referential buffer that cannot be moved.
#[derive(Debug)]
pub struct SelfReferentialBuffer {
data: [u8; 1024],
cursor: *mut u8,
_pin: PhantomPinned,
}
impl SelfReferentialBuffer {
pub fn new() -> Pin<Box<Self>> {
let buffer = SelfReferentialBuffer {
data: [0; 1024],
cursor: std::ptr::null_mut(),
_pin: PhantomPinned,
};
let mut pinned = Box::pin(buffer);
unsafe {
let mut_ref = Pin::get_unchecked_mut(pinned.as_mut());
mut_ref.cursor = mut_ref.data.as_mut_ptr();
}
pinned
}
pub fn read(&self, n_bytes: usize) -> &[u8] {
unsafe {
let start = self.data.as_ptr();
let end = start.add(self.data.len());
let cursor = self.cursor as *const u8;
assert!((start..=end).contains(&cursor), "cursor is out of bounds");
let offset = cursor.offset_from(start) as usize;
let available = self.data.len().saturating_sub(offset);
let len = n_bytes.min(available);
&self.data[offset..offset + len]
}
}
pub fn write(mut self: Pin<&mut Self>, bytes: &[u8]) {
let this = unsafe { self.as_mut().get_unchecked_mut() };
unsafe {
let start = this.data.as_mut_ptr();
let end = start.add(1024);
assert!((start..=end).contains(&this.cursor), "cursor is out of bounds");
let available = end.offset_from(this.cursor) as usize;
let len = bytes.len().min(available);
std::ptr::copy_nonoverlapping(bytes.as_ptr(), this.cursor, len);
this.cursor = this.cursor.add(len);
}
}
}
Note that the function signatures have now changed. For example, ::new()
returns Pin<Box<Self>> rather than Self. This incurs a heap allocation
because Pin<Ptr> must work with a pointer type like Box.
In ::new(), we use Pin::get_unchecked_mut() to get a mutable reference to
the buffer after it has been pinned. This is unsafe because we are breaking
the pinning guarantee for a moment to initialize the cursor. We must make sure
not to move the SelfReferentialBuffer after this point. The safety contract of
Pin is that once a value is pinned, its memory location is fixed until it is
dropped.