方便的'Option <Box <Any >>'确保成功后的访问权限?

ide*_*n42 6 any rust

在为通用接口编写回调时,对它们定义自己负责创建和访问的本地数据会很有用.

在CI中只使用一个void指针,类似于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 */ }
}
Run Code Online (Sandbox Code Playgroud)

在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 */ }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

  • 虽然只有一些工具定义自定义数据,但总是定义数据.
  • 设置数据时,按照惯例,该工具只能设置自己的数据.因此,没有机会获得错误的数据.
  • 任何时候没有遵循这些约定,它是一个错误,应该恐慌.

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


一些可能的选择:

  • 拆下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直到演员变量超出范围,我发现唯一可靠的方法是写一个宏.

use*_*342 1

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

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

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

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

  • 特征对象的“胖指针”表示具有与 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();
}
Run Code Online (Sandbox Code Playgroud)

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