Skip to content

Instantly share code, notes, and snippets.

@a10y
Created March 24, 2024 19:41
Show Gist options
  • Save a10y/5038160098ceb44f309d7f56a49b39cc to your computer and use it in GitHub Desktop.
Save a10y/5038160098ceb44f309d7f56a49b39cc to your computer and use it in GitHub Desktop.
Small exploration of Rust Pinning
use std::fmt;
use std::fmt::Formatter;
use std::marker::PhantomPinned;
use std::pin::{pin, Pin};
struct MyField {
name: String,
name_ptr: Option<*mut String>,
__pinned: PhantomPinned,
}
impl fmt::Debug for MyField {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
unsafe {
f.debug_struct("MyField")
.field("ptr", &format!("{:p}", self))
.field("name", &self.name)
.field("name_ptr", &self.name_ptr)
.field("name_ptr_stringified", &self.name_ptr.map(|str_ptr|
(*str_ptr).clone()).unwrap_or_default())
.finish()
}
}
}
impl MyField {
/// Initializer for our hidden field.
/// Can only be called from a Pinned context.
pub fn init(self: Pin<&mut Self>) {
let mut this = unsafe { self.get_unchecked_mut() };
this.name_ptr = Some(&mut this.name);
}
}
fn main() {
let mut value1 = MyField {
name: "hello_world".to_string(),
name_ptr: None,
__pinned: PhantomPinned,
};
let mut value2 = MyField {
name: "goodbye".to_string(),
name_ptr: None,
__pinned: PhantomPinned,
};
let mut value1 = pin!(value1);
let mut value2 = pin!(value2);
// Set internal pointers within the MyField structs
MyField::init(value1.as_mut());
MyField::init(value2.as_mut());
// Swap, breaking the Pin invariant
unsafe {
let ptr1 = value1.as_mut().get_unchecked_mut();
let ptr2 = value2.as_mut().get_unchecked_mut();
std::mem::swap(ptr1, ptr2);
}
println!("value1 = {value1:?}");
println!("value2 = {value2:?}");
// Output:
// ```
// value1 = MyField { ptr: "0x16d4ded50", name: "goodbye", name_ptr: Some(0x16d4ded90), name_ptr_stringified: "hello_world" }
// value2 = MyField { ptr: "0x16d4ded80", name: "hello_world", name_ptr: Some(0x16d4ded60), name_ptr_stringified: "goodbye" }
// ```
}
@a10y
Copy link
Author

a10y commented Mar 24, 2024

Pin allows you to describe types that have intrusive pointers or other memory-location-dependent fields, and have the compiler guarantee that the objects stay consistent, so long as you only use safe code.

The above example shows that the two objects can only be swapped in an unsafe context, which causes their internal name_ptr to point to the wrong location.

In the absence of me doing a malicious thing, by enforcing the types have methods that are only defined in the Pinned context ensures that the internal pointers should never point to invalid memory.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment