我遇到过一种情况,我有两个具有不同内部和叶节点类型的树数据结构。我想使用
Vec<PointInTrees>
维护一个向量,该向量保存指向叶子的指针,其定义如下:
enum PointInTrees{
InTreeA(*mut TreeNodeA),
InTreeB(*mut TreeNodeB),
}
其中任一树使用以下枚举来表示叶节点和内部节点:
enum TreeNodeA{
Internal(W)
Leaf(X)
}
enum TreeNodeB{
Internal(Y)
Leaf(Z)
我更喜欢
PointInTrees
的变体而不是指向 X
和 Z
的变体,但有时我需要能够在 TreeNode 类型上使用方法。这让我想知道是否可以将指向枚举数据的指针转换为指向枚举本身的指针 - 例如,当我知道它们都是叶子时,我宁愿不要不断地与我的 TreeNodeA
中的 Vec
进行模式匹配.
经过一番混乱之后,我意识到我可以使用以下example以我想要的方式进行转换,它执行指针算术来减去判别式。然而,这在实践中使用起来非常笨拙且容易出错。
use std::mem;
#[derive(Debug, Clone)]
enum Message{
Msg(String),
OtherMsg(String),
}
fn main() {
let discriminant_offset = mem::size_of::<isize>();
println!("Discriminant offset:\t\t {:?}", discriminant_offset);
let message_pointer: *mut Message = Box::into_raw(Box::new(Message::Msg("Hello".into())));
let other_message_pointer: *mut Message = Box::into_raw(Box::new(Message::OtherMsg("Hi".into())));
// Casting a String pointer to Message pointer for the Msg variant
if let Message::Msg(msg_string) = unsafe{&mut *message_pointer}{
let string_pointer: *mut String = msg_string;
let extracted_message_pointer: *mut Message = unsafe{(string_pointer as *mut u8).sub(discriminant_offset) as *mut Message};
println!("Initial pointer:\t\t {:?}, {:?}", message_pointer, unsafe{&*message_pointer});
println!("string pointer:\t\t\t {:?}, {:?}", string_pointer, unsafe{&*string_pointer} );
println!("extracted msg_pointer:\t\t {:?}, {:?}",extracted_message_pointer, unsafe{&*extracted_message_pointer});
}
println!();
// Casting a String pointer to Message pointer for the OtherMsg variant
if let Message::OtherMsg(other_msg_string) = unsafe{&mut *other_message_pointer}{
let other_string_pointer: *mut String = other_msg_string;
let extracted_other_message: *mut Message = unsafe{(other_string_pointer as *mut u8).sub(discriminant_offset) as *mut Message};
println!("Initial other pointer:\t\t {:?}, {:?}", other_message_pointer, unsafe{&*other_message_pointer});
println!("other string pointer:\t\t {:?}, {:?}", other_string_pointer, unsafe{&*other_string_pointer} );
println!("extracted other_msg_pointer:\t {:?}, {:?}", extracted_other_message, unsafe{&*extracted_other_message});
}
}
我的问题是如何使其坚固且符合人体工程学,是否已经有一些东西可以做到这一点?否则,我正在考虑编写一个宏,它为给定枚举的每种可能的变体指针类型实现 From 。
我知道编译器可以自由地使用判别式大小如果需要的话可以小于 isize 所以我需要某种方法在编译时可靠地获取判别式的大小。
您的目标有几个方面需要指出
add
/sub
等记录的规则,允许指向子对象的指针指向该子对象的 outside,只要它位于同一“分配的对象”内。在枚举变量持有指向值的情况下肯定会是这样。
但是,如果 Rust 采用 严格来源,那么这是不允许的。在严格的来源下,这样的指针和从它派生的任何指针只能访问该指针具有来源的内存(即仅叶类型)。
std::ptr
中的文档提供了有关严格来源的更多详细信息,但就目前情况而言,它仍然是一项实验性政策。我的理解是,Rust 团队会喜欢严格的出处,或者至少是比目前更严格的版本,但我不清楚什么时候会发生(如果有人有当前想法的链接,他们将不胜感激) .
所有数据类型(包括枚举判别式和变体有效负载)中字段的默认
Rust
布局均未指定,因此您当前依赖于未指定的行为,因此稍后编译时您的假设可能是错误的。不太可能,但技术细节很重要。
幸运的是,您只需要枚举上的
#[repr(C)]
即可为其提供指定的布局,甚至最好还指定判别式的大小:
#[derive(Debug, Clone)]
#[repr(C, u64)]
enum Message{
Msg(String),
OtherMsg(String),
}
这样布局就保证首先是可区分的,然后是变体数据(如果需要满足对齐,则在中间添加填充)。那么你就知道总会有一个固定的偏移量。请参阅类型布局了解更多信息。
至于以面向未来的方式实际推导 discriminant_offset
的
value,我不知道。 @eggyal 在评论中建议对
offset_of!
的改进可能会使这在未来变得相当微不足道。但现在,你所拥有的
std::mem::size_of
应该没问题(尽管我建议使用像 u64
这样的非可变大小),但前提是且仅当判别式大小与变体数据的对齐方式匹配时。
因此,为了确保这一点,我建议添加一些静态断言,以便在这些假设发生变化时生成编译器错误。
const _: () = { assert!(std::mem::size_of::<u64>() == std::mem::align_of::<String>()); };