在编写通用接口的回调函数时,定义自己的本地数据并负责创建和访问这些数据可能会很有用。
在C语言中,我只会使用void指针,如下所示:
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并不危险,因为:
- 虽然只有一些工具定义了自定义数据,但定义了自定义数据的工具总是定义了它。
- 当设置数据时,按照惯例工具只会设置自己的数据。所以没有机会得到错误的数据。
- 任何时候如果违反这些约定,那么就是一个bug,应该panic。
一些可能的选项:
- 删除Option,只使用Box<Any>,使用Box::new(())表示None,这样可以稍微简化访问。
- 使用宏或函数隐藏冗长性-当然可以使用传递Option<Box<Any>>的方式,但最好不要这样做-将其作为最后的选择。
- 向Option<Box<Any>>添加一个trait,该trait公开一个方法,例如tool.custom_data.unwrap_box::<StructOnlyForThisTool>(),并带有匹配的unwrap_box_mut。
更新1):自提出这个问题以来,我没有包括一个看似相关的点。可能会有多个回调函数(如execute),它们都必须能够访问custom_data。当时我认为这并不重要。
更新2):将其包装在一个取tool的函数中并不实用,因为借用检查器会阻止直到转换变量超出范围之前无法进一步访问tool的成员,我发现唯一可靠的方法是编写一个宏。
SomeTool
创建访问器方法怎么样?custom_data()
和custom_data_mut()
。 - Jorge Israel PeñaOption<Box<Any>>
作为参数?例如:some_box_any_unwrap_as::<StructOnlyForThisTool>(&tool.custom_data)
。虽然我可以编写一个可行的宏或函数,但它们感觉有点笨拙 - 特别是如果这是供他人使用的API。 - ideasman42