使用 transmute 的递归数据结构的零成本构建器模式。这样安全吗?有更好的方法吗?
Zero cost builder pattern for recursive data structure using transmute. Is this safe? Is there a better approach?
我想使用构建器模式创建一个 struct
,构建器模式必须在构建之前进行验证,并且我想尽量减少构建开销。
我使用 std::mem::transmute
想出了一个很好的方法来做到这一点,但我不确定这种方法是否真的安全,或者它是否是最好的方法。
这是我的代码:(Rust Playground)
#[derive(Debug)]
pub struct ValidStruct {
items: Vec<ValidStruct>
}
#[derive(Debug)]
pub struct Builder {
pub items: Vec<Builder>
}
#[derive(Debug)]
pub struct InvalidStructError {}
impl Builder {
pub fn new() -> Self {
Self { items: vec![] }
}
pub fn is_valid(&self) -> bool {
self.items.len() % 2 == 1
}
pub fn build(self) -> Result<ValidStruct, InvalidStructError> {
if !self.is_valid() {
return Err(InvalidStructError {});
}
unsafe {
Ok(std::mem::transmute::<Builder, ValidStruct>(self))
}
}
}
fn main() {
let mut builder = Builder::new();
builder.items.push(Builder::new());
let my_struct = builder.build().unwrap();
println!("{:?}", my_struct)
}
所以,这似乎可行。我认为它应该是安全的,因为我知道这两个结构是相同的。我错过了什么吗?这实际上会不会以某种方式引起问题,或者是否有可用的 cleaner/better 方法?
您通常不能仅仅因为不同结构似乎具有相同顺序的相同字段而在不同结构之间进行转换,因为编译器可能会更改它。您可以通过强制内存布局来避免风险,但您随后会与编译器作斗争并阻止优化。这种方法通常不被推荐,在我看来,这里不需要。
你想要的就是拥有
- 具有 public 字段的递归数据结构,以便您可以轻松构建它
- 一个相同的结构,从第一个构建,但没有 public 访问权限,并且仅在第一个验证后构建
出于性能原因,您希望避免无用的副本。
我的建议是使用包装器 class。这是有道理的,因为在 Rust 中将一个结构包装在另一个结构中是完全没有成本的。
这样你就可以
/// 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
声明为 Data
的包装器,但可以 public 访问其字段)
我想使用构建器模式创建一个 struct
,构建器模式必须在构建之前进行验证,并且我想尽量减少构建开销。
我使用 std::mem::transmute
想出了一个很好的方法来做到这一点,但我不确定这种方法是否真的安全,或者它是否是最好的方法。
这是我的代码:(Rust Playground)
#[derive(Debug)]
pub struct ValidStruct {
items: Vec<ValidStruct>
}
#[derive(Debug)]
pub struct Builder {
pub items: Vec<Builder>
}
#[derive(Debug)]
pub struct InvalidStructError {}
impl Builder {
pub fn new() -> Self {
Self { items: vec![] }
}
pub fn is_valid(&self) -> bool {
self.items.len() % 2 == 1
}
pub fn build(self) -> Result<ValidStruct, InvalidStructError> {
if !self.is_valid() {
return Err(InvalidStructError {});
}
unsafe {
Ok(std::mem::transmute::<Builder, ValidStruct>(self))
}
}
}
fn main() {
let mut builder = Builder::new();
builder.items.push(Builder::new());
let my_struct = builder.build().unwrap();
println!("{:?}", my_struct)
}
所以,这似乎可行。我认为它应该是安全的,因为我知道这两个结构是相同的。我错过了什么吗?这实际上会不会以某种方式引起问题,或者是否有可用的 cleaner/better 方法?
您通常不能仅仅因为不同结构似乎具有相同顺序的相同字段而在不同结构之间进行转换,因为编译器可能会更改它。您可以通过强制内存布局来避免风险,但您随后会与编译器作斗争并阻止优化。这种方法通常不被推荐,在我看来,这里不需要。
你想要的就是拥有
- 具有 public 字段的递归数据结构,以便您可以轻松构建它
- 一个相同的结构,从第一个构建,但没有 public 访问权限,并且仅在第一个验证后构建
出于性能原因,您希望避免无用的副本。
我的建议是使用包装器 class。这是有道理的,因为在 Rust 中将一个结构包装在另一个结构中是完全没有成本的。
这样你就可以
/// 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
声明为 Data
的包装器,但可以 public 访问其字段)