Created
March 24, 2024 19:41
-
-
Save a10y/5038160098ceb44f309d7f56a49b39cc to your computer and use it in GitHub Desktop.
Small exploration of Rust Pinning
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" } | |
// ``` | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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 internalname_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
Pin
ned context ensures that the internal pointers should never point to invalid memory.