RBF*_*F06 4 string dynamic-dispatch rust trait-objects
我有一组结构 、A、B、C和D,它们都实现了一个 Trait\n Runnable。
trait Runnable {\n fn run(&mut self);\n}\nimpl Runnable for A {...}\nimpl Runnable for B {...}\nimpl Runnable for C {...}\nimpl Runnable for D {...}\nRun Code Online (Sandbox Code Playgroud)\n我还有一个结构体,用作构造、\n 、和实例Config的规范。ABCD
struct Config {\n filename: String,\n other_stuff: u8,\n}\n\nimpl From<Config> for A {...}\nimpl From<Config> for B {...}\nimpl From<Config> for C {...}\nimpl From<Config> for D {...}\nRun Code Online (Sandbox Code Playgroud)\n在我的程序中,我想解析一个Config实例并根据字段的值构造A,\n B, C, 或,然后对其进行调用\n 。应通过根据字符串顺序检查每个结构并选择第一个“匹配”该字符串的结构来选择结构。DfilenameRunnable::runfilename
这是一个 na\xc3\xafve 实现。
\ntrait CheckFilename {\n fn check_filename(filename: &str) -> bool;\n}\nimpl CheckFilename for A {...}\nimpl CheckFilename for B {...}\nimpl CheckFilename for C {...}\nimpl CheckFilename for D {...}\n\n\nfn main() {\n let cfg: Config = get_config(); // Some abstract way of evaluating a Config at runtime.\n\n let mut job: Box<dyn Runnable> = if A::check_filename(&cfg.filename) {\n println!("Found matching filename for A");\n Box::new(A::from(cfg))\n } else if B::check_filename(&cfg.filename) {\n println!("Found matching filename for B");\n Box::new(B::from(cfg))\n } else if C::check_filename(&cfg.filename) {\n println!("Found matching filename for C");\n Box::new(C::from(cfg))\n } else if D::check_filename(&cfg.filename) {\n println!("Found matching filename for D");\n Box::new(D::from(cfg))\n } else {\n panic!("did not find matching pattern for filename {}", cfg.filename);\n };\n\n job.run();\n}\nRun Code Online (Sandbox Code Playgroud)\n这可行,但有一些代码味道:
\nif else if else if else if else...声明很臭if D::check_filename(&cfg.filename) {\n println!("Found matching filename for D");\n Box::new(B::from(cfg)) // Developer error: constructs a B instead of a D.\n}\nRun Code Online (Sandbox Code Playgroud)\n编译器不会捕获这一点。E、等)不太符合人体工程学。它需要为主语句中的每个语句添加一个新分支。简单地将结构添加到某种结构类型的“主列表”中会更好。FGif else是否有更优雅或更惯用的方法来解决这些气味?
\n由于转换会消耗Config,统一所有类型逻辑的挑战是您需要有条件地移动配置值才能进行转换。标准库有多种可能出错的消费函数的情况,它们使用的模式是 return Result,返还该Err情况下可能消费的值。例如,Arc::try_unwrap提取 an 的内部值Arc,但如果失败,则会Arc在Err变体中给出后面的值。
我们可以在这里做同样的事情,创建一个函数,如果文件名匹配,它会生成适当的结构之一,但在出现错误时返回配置:
fn try_convert_config_to<T>(config: Config) -> Result<Box<dyn Runnable>, Config>
where
T: Runnable + CheckFilename + 'static,
Config: Into<T>,
{
if T::check_filename(&config.filename) {
Ok(Box::new(config.into()))
} else {
Err(config)
}
}
Run Code Online (Sandbox Code Playgroud)
然后,您可以编写另一个函数,其中包含该函数的特定实例化的静态切片,并且它可以按顺序尝试每个函数,直到成功为止。由于我们将配置移动到每个加载器函数中,因此我们必须将其放回容器中,Err以便下一个循环迭代可以再次移动它。
fn try_convert_config(mut config: Config) -> Option<Box<dyn Runnable>> {
static CONFIG_LOADERS: &[fn(Config) -> Result<Box<dyn Runnable>, Config>] = &[
try_convert_config_to::<A>,
try_convert_config_to::<B>,
try_convert_config_to::<C>,
try_convert_config_to::<D>,
];
for loader in CONFIG_LOADERS {
match loader(config) {
Ok(c) => return Some(c),
Err(c) => config = c,
};
}
None
}
Run Code Online (Sandbox Code Playgroud)
这解决了您所有的担忧:
try_convert_config_to一次性实现了所有类型的逻辑。check_filename使用.intotry_convert_config_toCONFIG_LOADERS切片添加新元素即可。(游乐场)
| 归档时间: |
|
| 查看次数: |
138 次 |
| 最近记录: |