3

For a struct member its possible to calculate the offsetof in Rust, similar to C's offsetof.

While this works for struct fields, I couldn't find an equivalent of how to do this for enums and their variant members.

From talking to developers on IRC its not guaranteed that all members of an enum are aligned:

How to calculate the offset of an enum member?


With instances it could work like this:

enum Test { A(u8), B(f64) };

fn test_me(a: Test) {
    if let Test::A(b) = a {
        // we could find the offset between 'a' and 'b' here.
        // but how to do this without instantiating variables?
        println("{}", (b as *const _) as usize - (a as *const _) as usize);
    }
}

However the aim is to be able to do this by inspecting only the type, so it could compile down to a constant, eg:

println("{}", offset_of_enum!(Test, A));

While attempting to write a macro for this I ran into problems joining the arguments by :: so I wasn't sure how to resolve that part.

Community
  • 1
  • 1
ideasman42
  • 42,413
  • 44
  • 197
  • 320
  • 3
    Why do you need to do this? It seems like a strange thing to want other than through curiosity. – Chris Emerson Jan 24 '17 at 10:21
  • To access the struct that contains the enum. Its used directly in a struct that has some general information, would be handy to include in debug/warning messages, otherwise not essential. (the struct is read-only so should be safe in this context). – ideasman42 Jan 24 '17 at 11:16
  • 1
    So you start off with a reference to the enum or a reference to the variant member? If it's the first, then you don't need this at all. – oli_obk Jan 24 '17 at 11:39
  • 2
    And if it's the latter I'd first try to find a different way around it. – Chris Emerson Jan 24 '17 at 13:36
  • @ker, its variant member, updated questions. – ideasman42 Jan 24 '17 at 20:37

1 Answers1

4

Enum variants differ heavily from struct fields. An enum variant does not have a unique type that's different from the enum's type. Even internally in the Rust compiler enum variants are represented including the discriminant of the enum. This means that an enum variant's offset to the enum itself is zero.

More likely you want the offset of an enum variant's field. Since the only way to obtain a reference to an enum variant's field is to match on a value of an enum, you'd need a valid enum value to match on, so you can't use the nullpointer trick used in the struct field offset calculation.

macro_rules! offset_of {
    ($($tt:tt)*) => {
        {
            let base = $($tt)*(unsafe { ::std::mem::uninitialized() });
            let offset = match base {
                $($tt)*(ref inner) => (inner as *const _ as usize) - (&base as *const _ as usize),
                _ => unreachable!(),
            };
            ::std::mem::forget(base);
            offset
        }
    }
}
enum Foo {
    A(i32),
    B(u8),
}
let offset = offset_of!(Foo::A);

It is left to the reader to implement this macro for enum struct variants and enum tuple variants with more than one field.

oli_obk
  • 28,729
  • 6
  • 82
  • 98
  • I think you've got a missing `std::mem::forget` in your macro to discard `base`; wouldn't want `Drop` to be executed on uninitialized memory. An easy way to test this is put a `String` in `A` and scramble the memory of `base` (memset it with `0x8F` for example). – Matthieu M. Jan 24 '17 at 09:23
  • It doesn't appear to be working for enums with multiple fields. I added `, ..` in the match and this works both single and multiple fields, however the initialization fails for multiple fields and it's not obvious how to deal with that :( – Matthieu M. Jan 24 '17 at 10:32
  • yes, you need to give a hint in the macro invocation as to how many fields the enum variant has. This is impossible to work around to the best of my knowledge. – oli_obk Jan 24 '17 at 11:32
  • Just tested this for real-world use, works well. Also double-checked and this compiles down to a constant with optimized builds (no actual `std::mem` use). Would have assumed so, but nice to confirm. – ideasman42 Jan 24 '17 at 21:47
  • Do you think rewriting this with `MaybeUninit` instead of `mem::uninitilialiazed` could get rid of the unsafe? – hellow May 12 '22 at 09:42