Skip to content

Instantly share code, notes, and snippets.

Created May 1, 2016 17:55
Show Gist options
  • Save anonymous/efd8ac344324da2a18ca9bf845aefd9a to your computer and use it in GitHub Desktop.
Save anonymous/efd8ac344324da2a18ca9bf845aefd9a to your computer and use it in GitHub Desktop.
Shared via Rust Playground
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