Rust 使用transmute的递归数据结构的零成本构建器模式。这安全吗?有更好的方法吗?

Rust 使用transmute的递归数据结构的零成本构建器模式。这安全吗?有更好的方法吗?,rust,builder,unsafe,recursive-datastructures,Rust,Builder,Unsafe,Recursive Datastructures,我想使用构建器模式创建一个struct,构建器模式必须在构建之前进行验证,并且我想尽量减少构建开销 我已经想出了一个使用std::mem::transmute实现这一点的好方法,但我对这种方法是否真的安全,或者它是否是最好的方法还远没有信心 这是我的代码:() #[派生(调试)] pub结构ValidStruct{ 项目:Vec } #[导出(调试)] 发布结构生成器{ 发布项目:Vec } #[导出(调试)] 发布结构InvalidStructError{} impl生成器{ pub fn n

我想使用构建器模式创建一个
struct
,构建器模式必须在构建之前进行验证,并且我想尽量减少构建开销

我已经想出了一个使用
std::mem::transmute
实现这一点的好方法,但我对这种方法是否真的安全,或者它是否是最好的方法还远没有信心

这是我的代码:()

#[派生(调试)]
pub结构ValidStruct{
项目:Vec
}
#[导出(调试)]
发布结构生成器{
发布项目:Vec
}
#[导出(调试)]
发布结构InvalidStructError{}
impl生成器{
pub fn new()->Self{
Self{items:vec![]}
}
发布fn是有效的(&self)->bool{
self.items.len()%2==1
}
发布fn构建(自)->结果{
if!self.u是否有效(){
返回Err(InvalidStructError{});
}
不安全{
Ok(std::mem::transmute:(self))
}
}
}
fn main(){
让mut builder=builder::new();
push(builder::new());
让我的_struct=builder.build().unwrap();
println!(“{:?}”,我的结构)
}

所以,这似乎奏效了。我认为它应该是安全的,因为我知道这两个结构是相同的。我遗漏了什么吗?这是否真的会导致问题,或者是否有更干净/更好的方法可用?

您通常不能仅仅因为不同的结构似乎具有相同顺序的相同字段而在它们之间进行转换,因为编译器可能会改变这一点。您可以通过强制内存布局来避免这种风险,但这样做会与编译器发生冲突并阻止优化。这种方法通常不被推荐,在我看来,这里也不需要

你想要的是

  • 带有公共字段的递归数据结构,以便轻松构建
  • 一个相同的结构,从第一个开始建造,但没有公共通道,仅在第一个验证后建造
而且,出于性能原因,您希望避免无用的拷贝

我建议创建一个包装器类。这是有道理的,因为将一个结构包裹在另一个结构中完全没有成本

你本来可以这样做的

/// This is the "Builder" struct
pub struct Data {
    pub items: Vec<Data>,
}
pub struct ValidStruct {
    data: Data, // no public access here
}
impl Data {
    pub fn build(self) -> Result<ValidStruct, InvalidStructError> {
        if !self.is_valid() {
            return Err(InvalidStructError {});
        }
        Ok(Self{ data })
    }
}
///这是“Builder”结构
发布结构数据{
酒吧项目:Vec,
}
pub结构ValidStruct{
data:data,//此处没有公共访问权限
}
impl数据{
发布fn构建(自)->结果{
if!self.u是否有效(){
返回Err(InvalidStructError{});
}
Ok(自{data})
}
}

(或者,您也可以将结构
Builder
声明为
数据的包装,但具有对其字段的公共访问)

结构在代码中是相同的,但在编译后字段的顺序可能会有所不同(假设实际用例中有更多)。你在这里的目标是什么?做一个更快的构建?那么这种优化最好由编译器来完成。@DenysSéguret-我可以使用
#[repr(C)]
来避免字段顺序问题,对吗?我想把优化留给编译器。但是如果我进入并递归地转换每个
,我很确定编译器不会进行我想要的优化。有更好的方法吗?这里您的想法是,构建器具有相同的字段,但是是公共的,而ValidStruct是相同的,但是经过检查的?为什么不包装临时对象(包装是免费的)?因此,ValidStruct可以是
struct ValidStruct{data:data}
,数据具有公共字段,并且是builder.Ohh-目标是避免O(n)运行时性能运行
build
。原则上,它应该是O(1),但如果我必须递归地进入项目,它就不会是。你是对的。无论如何,验证都必须是递归的。我认为你的包装方法正是我想要的。如果你想写下来作为答案,我会接受的!
/// This is the "Builder" struct
pub struct Data {
    pub items: Vec<Data>,
}
pub struct ValidStruct {
    data: Data, // no public access here
}
impl Data {
    pub fn build(self) -> Result<ValidStruct, InvalidStructError> {
        if !self.is_valid() {
            return Err(InvalidStructError {});
        }
        Ok(Self{ data })
    }
}