Created
May 1, 2016 17:55
-
-
Save anonymous/efd8ac344324da2a18ca9bf845aefd9a to your computer and use it in GitHub Desktop.
Shared via Rust Playground
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::marker::PhantomData; | |
use std::mem; | |
use std::ops::Add; | |
// Represents a pointer to a field of type `U` within the type `T` | |
#[derive(Debug)] | |
struct FieldOffset<T, U>( | |
// Offset in bytes of the field within the struct | |
usize, | |
// A pointer-to-member can be thought of as a function from | |
// `&T` to `&U` with matching lifetimes | |
PhantomData<for<'a> Fn(&'a T) -> &'a U> | |
); | |
impl<T, U> FieldOffset<T, U> { | |
// Unsafe constructor | |
pub unsafe fn new<F: for<'a> FnOnce(&'a T) -> &'a U>(f: F) -> Self { | |
// Construct a "fake" T. It's not valid, but the lambda shouldn't | |
// actually access it (which is why this is unsafe) | |
let ref x = mem::zeroed(); | |
// Pass a reference to the zeroed T to the lambda | |
// The lambda gives us back a reference to (what we hope is) | |
// a field of T, of type U | |
let y = f(x); | |
// Compute the offset of the field via the difference between the | |
// references `x` and `y`. Overflow is an error: in debug builds it | |
// will be caught here, in release it will wrap around and be caught | |
// on the next line. | |
let offset = (y as *const U as usize) - (x as *const T as usize); | |
// Sanity check: ensure that the field offset plus the field size | |
// is no greater than the size of the containing struct. This is | |
// not sufficient to make the function *safe*, but it does catch | |
// obvious errors like returning a reference to a boxed value, | |
// which is owned by `T` and so has the correct lifetime, but is not | |
// actually a field. | |
assert!(offset + mem::size_of::<U>() <= mem::size_of::<T>()); | |
// Construct an instance using the offset | |
FieldOffset(offset, PhantomData) | |
} | |
// Apply the pointer-to-member to an instance of `T` | |
pub fn apply<'a>(&self, x: &'a T) -> &'a U { | |
unsafe { | |
// Simply add the precalculated offset and cast to a U | |
&*(((x as *const T as usize) + self.0) as *const U) | |
} | |
} | |
// Equivalent for mutable references | |
pub fn apply_mut<'a>(&self, x: &'a mut T) -> &'a mut U { | |
unsafe { | |
&mut *(((x as *mut T as usize) + self.0) as *mut U) | |
} | |
} | |
} | |
// Allow chaining pointer-to-members | |
impl<T, U, V> Add<FieldOffset<U, V>> for FieldOffset<T, U> { | |
type Output = FieldOffset<T, V>; | |
fn add(self, other: FieldOffset<U, V>) -> FieldOffset<T, V> { | |
FieldOffset(self.0 + other.0, PhantomData) | |
} | |
} | |
// This macro allows safe construction of a FieldOffset, | |
// by generating a known to be valid lambda to pass to the | |
// constructor. It takes a type and an identifier of a field | |
// within that type as input. | |
macro_rules! offset_of { | |
($t: ty, $f: ident) => ( | |
unsafe { FieldOffset::<$t, _>::new(|x| &x.$f) } | |
) | |
} | |
// Example structs | |
#[derive(Default, Debug)] | |
struct Foo { | |
a: u32, | |
b: f64, | |
c: bool | |
} | |
#[derive(Default, Debug)] | |
struct Bar { | |
x: u32, | |
y: Foo, | |
} | |
fn main() { | |
// Get a pointer to `b` within `Foo` | |
let foo_b = offset_of!(Foo, b); | |
let bar_y = offset_of!(Bar, y); | |
// Construct an example `Foo` | |
let mut x = Bar { | |
x: 0, | |
y: Foo { | |
a: 1, | |
b: 2.0, | |
c: false | |
} | |
}; | |
// Combine the pointer-to-members | |
let bar_y_b = bar_y + foo_b; | |
// Apply the pointer to get at `b` and mutate it | |
{ | |
let y = bar_y_b.apply_mut(&mut x); | |
*y = 42.0; | |
} | |
// Print out the result | |
println!("{:?}", bar_y_b); | |
println!("{:?}", x); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment