gpt4 book ai didi

rust - 方便 'Option>' 访问就一定成功?

转载 作者:行者123 更新时间:2023-11-29 07:48:53 25 4
gpt4 key购买 nike

在为通用接口(interface)编写回调时,定义他们自己负责创建和访问的本地数据可能很有用。

在 C 中,我只使用一个空指针,类 C 示例:

struct SomeTool {
int type;
void *custom_data;
};

void invoke(SomeTool *tool) {
StructOnlyForThisTool *data = malloc(sizeof(*data));
/* ... fill in the data ... */
tool.custom_data = custom_data;
}
void execute(SomeTool *tool) {
StructOnlyForThisTool *data = tool.custom_data;
if (data.foo_bar) { /* do something */ }
}

当用 Rust 编写类似的东西时,替换 void *Option<Box<Any>> ,但是我发现访问数据过于冗长,例如:

struct SomeTool {
type: i32,
custom_data: Option<Box<Any>>,
};

fn invoke(tool: &mut SomeTool) {
let data = StructOnlyForThisTool { /* my custom data */ }
/* ... fill in the data ... */
tool.custom_data = Some(Box::new(custom_data));
}
fn execute(tool: &mut SomeTool) {
let data = tool.custom_data.as_ref().unwrap().downcast_ref::<StructOnlyForThisTool>().unwrap();
if data.foo_bar { /* do something */ }
}

这里有一行我希望能够以更紧凑的方式编写:

  • tool.custom_data.as_ref().unwrap().downcast_ref::<StructOnlyForThisTool>().unwrap()
  • tool.custom_data.as_ref().unwrap().downcast_mut::<StructOnlyForThisTool>().unwrap()

虽然每个方法本身都有意义,但在实践中,我不想在整个代码库中编写它,也不是我想要经常输入或容易记住的东西。

按照惯例,在这里使用 unwrap 并不危险,因为:

  • 虽然只有一些工具定义自定义数据,但始终定义自定义数据的工具。
  • 设置数据后,按照惯例,工具只会设置自己的数据。所以不可能有错误的数据。
  • 任何时候不遵守这些约定,都是错误,应该引起 panic 。

鉴于这些约定,并假设从工具访问自定义数据是经常做的事情 - 什么是简化此表达式的好方法?


一些可能的选择:

  • 删除 Option , 只需使用 Box<Any>Box::new(())代表None因此可以稍微简化访问。
  • 使用宏或函数来隐藏冗长 - 传入 Option<Box<Any>> :当然可以,但最好不要 - 将作为最后的手段使用。
  • 将特征添加到 Option<Box<Any>>它公开了一个方法,例如 tool.custom_data.unwrap_box::<StructOnlyForThisTool>()与匹配 unwrap_box_mut .

更新 1):自问这个问题以来,我没有包括的一点似乎是相关的。可能有多个回调函数,如 execute这必须都能够访问 custom_data .当时我认为指出这一点并不重要。

更新 2):将其包装在一个接受 tool 的函数中这是不切实际的,因为借用检查器会阻止进一步访问 tool 的成员在强制转换变量超出范围之前,我发现唯一可靠的方法是编写一个宏。

最佳答案

如果实现实际上只有一个名称类似于execute 的方法,则强烈表明要考虑使用闭包来捕获实现数据。 SomeTool 可以使用盒装的 FnMut 以类型删除的方式合并任意可调用对象,如图所示 in this answer . execute() 然后归结为使用 (self.impl_)() 调用存储在结构字段实现闭包中的闭包。对于更通用的方法,当您有更多的实现方法时,它也将起作用,请继续阅读。

type+dataptr C 模式的一种惯用且类型安全的等价物是将实现类型和指向数据的指针一起存储为 trait object。 . SomeTool 结构可以包含一个字段,一个装箱的 SomeToolImpl 特征对象,其中特征指定特定于工具的方法,例如 execute。这具有以下特点:

  • 您不再需要显式的 type 字段,因为运行时类型信息已包含在特征对象中。

  • 特征方法的每个工具实现都可以以类型安全的方式访问自己的数据,无需转换或展开。这是因为 trait 对象的 vtable 会自动为正确的 trait 实现调用正确的函数,而尝试调用不同的函数是编译时错误。

  • trait 对象的“胖指针”表示与 type+dataptr 对具有相同的性能特征 - 例如,SomeTool 的大小将是两个指针,并且访问实现数据仍将涉及单个指针取消引用。

这是一个示例实现:

struct SomeTool {
impl_: Box<SomeToolImpl>,
}

impl SomeTool {
fn execute(&mut self) {
self.impl_.execute();
}
}

trait SomeToolImpl {
fn execute(&mut self);
}

struct SpecificTool1 {
foo_bar: bool
}

impl SpecificTool1 {
pub fn new(foo_bar: bool) -> SomeTool {
let my_data = SpecificTool1 { foo_bar: foo_bar };
SomeTool { impl_: Box::new(my_data) }
}
}

impl SomeToolImpl for SpecificTool1 {
fn execute(&mut self) {
println!("I am {}", self.foo_bar);
}
}

struct SpecificTool2 {
num: u64
}

impl SpecificTool2 {
pub fn new(num: u64) -> SomeTool {
let my_data = SpecificTool2 { num: num };
SomeTool { impl_: Box::new(my_data) }
}
}

impl SomeToolImpl for SpecificTool2 {
fn execute(&mut self) {
println!("I am {}", self.num);
}
}

pub fn main() {
let mut tool1: SomeTool = SpecificTool1::new(true);
let mut tool2: SomeTool = SpecificTool2::new(42);
tool1.execute();
tool2.execute();
}

请注意,在此设计中,将实现作为选项 没有意义,因为我们始终将工具类型 与实现相关联。虽然实现没有数据是完全有效的,但它必须始终具有与其关联的类型。

关于rust - 方便 'Option<Box<Any>>' 访问就一定成功?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41156227/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com