如何以字节为单位获取枚举成员的指针偏移量?

3
对于Rust中的结构体成员,可以像C语言的offsetof一样计算offsetof。虽然这适用于结构体字段,但我找不到类似的方法来处理枚举及其变量成员。
从与IRC上的开发人员交谈中得知,并不能保证枚举的所有成员都对齐:
如何计算枚举成员的偏移量?

使用实例可能会像这样工作:

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);
    }
}

然而,目的是仅通过检查类型来实现此操作,因此它可以编译为常量,例如:
println("{}", offset_of_enum!(Test, A));

尝试编写宏时,我遇到了一个问题,无法通过::连接参数,因此我不确定如何解决这一部分。

3
为什么你需要这样做?除了出于好奇心,这似乎是一件奇怪的事情。 - Chris Emerson
访问包含枚举的结构体。它直接用于包含一些通用信息的结构体中,可以方便地包含在调试/警告消息中,否则不是必需的。(该结构体为只读,在此上下文中应该是安全的)。 - ideasman42
1
你是从枚举的引用还是变量成员的引用开始的?如果是前者,那么你根本不需要这个。 - oli_obk
2
如果是后者,我会首先尝试找到另一种解决方法。 - Chris Emerson
@ker,它的变量成员,更新了问题。 - ideasman42
1个回答

4

枚举变体与结构字段有很大的区别。枚举变体没有与枚举类型不同的唯一类型。即使在Rust编译器内部,枚举变量也是包括枚举的标签表示的。这意味着枚举变体相对于枚举本身的偏移为零。

更有可能的情况是,您需要获取枚举变体字段的偏移量。由于获取枚举变体字段的唯一方法是匹配枚举值,您需要一个有效的枚举值来进行匹配,因此无法使用在结构体字段偏移量计算中使用的nullpointer技巧。

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);

由读者自行实现此宏用于结构变量、元组变量中包含多个字段的枚举类型。


我认为你的宏中缺少了 std::mem::forget 来丢弃 base;不想在未初始化的内存上执行 Drop。测试这个问题的简单方法是将一个 String 放入 A 中,并混淆 base 的内存(例如,使用 0x8F 填充)。 - Matthieu M.
似乎对于具有多个字段的枚举类型无效。我在匹配中添加了, ..,这适用于单个和多个字段,但是对于多个字段的初始化失败,并且不明显如何处理它:( - Matthieu M.
是的,你需要在宏调用中给出提示,以表明枚举变体有多少个字段。据我所知,这是不可能避免的。 - oli_obk
刚刚测试了一下,实际使用效果很好。同时进行了双重检查,这可以编译成一个常量,并且在优化构建时不需要实际的std::mem使用。本来就是这样假定的,但确认一下还是很好的。 - ideasman42
你认为用MaybeUninit重写这个代码,可以消除不安全因素吗? - hellow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接