我有两个独立的库(不同的crates),每个都定义了一个struct和一个构建这样一个struct的函数,如果可能的话(为了简单起见,我将所有内容都放在一个模块中):
struct Foo {}
struct Bar {}
impl Foo {
fn make_foo(a: &str) -> Option<Foo> {
Some(Foo {})
}
}
impl Bar {
fn make_bar(b: &str) -> Option<Bar> {
Some(Bar {})
}
}
我现在想以相同的方式处理这两个结构,而不管我是否真的有Foo
或Bar
:
trait SomeTrait {
fn do_something() -> ();
}
impl SomeTrait for Foo {
fn do_something() -> () {
()
}
}
impl SomeTrait for Bar {
fn do_something() -> () {
()
}
}
由于SomeTrait
的大小在编译时是未知的,我不能简单let a: Option<SomeTrait> = Foo::make_foo("abc")
。所以我尝试将其包装在Box
(之前解压缩Option
)中:
fn main() {
let f: Option<Box<SomeTrait>> = Foo::make_foo("abc").map(Box::new)
}
但编译器仍抱怨:
error: mismatched types [E0308]
let b: Option<Box<SomeTrait>> = Foo::new().map(Box::new);
^~~~~~~~~~~~~~~~~~~~~~~~
help: run `rustc --explain E0308` to see a detailed explanation
note: expected type `std::option::Option<Box<SomeTrait>>`
note: found type `std::option::Option<Box<Foo>>`
我尝试使用as
进行投射,但由于这些都是非标量类型,因此无法正常工作。
如何解决此问题(不涉及Foo
和Bar
的实施)?我觉得我试图将经典OOP语言的模式应用到Rust的特质系统。
最后,我希望能够做到这样的事情:
fn main() {
let arg = 5;
let x: Option<Box<SomeTrait>> = match arg {
4 => Foo::make_foo("abc").map(Box::new),
_ => Bar::make_bar("abc").map(Box::new),
};
x.do_something()
}
完整示例:
// Library part
struct Foo {}
struct Bar {}
impl Foo {
fn new() -> Option<Foo> {
Some(Foo {})
}
}
impl Bar {
fn new() -> Option<Bar> {
Some(Bar {})
}
}
// My Part
trait SomeTrait {
fn do_something(&self);
}
impl SomeTrait for Foo {
fn do_something(&self) {
println!("foo")
}
}
impl SomeTrait for Bar {
fn do_something(&self) {
println!("bar")
}
}
fn main() {
let b: Option<Box<SomeTrait>> = match "x" {
"x" => Foo::new().map(Box::new),
_ => Bar::new().map(Box::new),
};
b.unwrap().do_something();
}
答案 0 :(得分:4)
这有两个普遍的问题。第一个是你需要将对象表示为Option<Box<SomeTrait>>
,所以设置你的变量绑定需要像这样let f: Option<Box<SomeTrait>> = Some(Box::new(Foo::make_foo("abc")));
并且你需要从make方法中删除选项部分,以便以后可以正确地包装它。
impl Foo {
fn make_foo(a: &str) -> Foo {
Foo {}
}
}
此处的订购非常重要。选项不能保持未标注的特征,因此需要包装盒子。
其次,如果特征具有不参考&self
除了生锈错误文档之外的快速显示
方法没有接收器
不能调用不带自身参数的方法,因为它不会成为获取方法表指针的方法。
因此,对于你需要的特质方法
trait SomeTrait {
fn do_something(&self);
}
impl SomeTrait for Foo {
fn do_something(&self) { }
}
impl SomeTrait for Bar {
fn do_something(&self) { }
}
编辑:
由于您无法更改make函数,另一种正确映射它们的方法是向lambda添加返回类型,以便它知道如何帮助编译器同意该类型。
let f: Option<Box<SomeTrait>> = Foo::make_foo("abc").and_then(|foo| -> Option<Box<SomeTrait>> {
Some(Box::new(foo))
});
答案 1 :(得分:3)
我不知道这是否真实,但我得到了这段代码:
// Library part
struct Foo {}
struct Bar {}
impl Foo {
fn new() -> Option<Foo> {Some(Foo {})}
}
impl Bar {
fn new() -> Option<Bar> {Some(Bar {})}
}
// My Part
trait SomeTrait {
fn do_something(&self);
}
impl SomeTrait for Foo {
fn do_something(&self) {
println!("foo")
}
}
impl SomeTrait for Bar {
fn do_something(&self) {
println!("bar")
}
}
fn main () {
let b: Option<Box<SomeTrait>> = match "x" {
"x" => Foo::new().map(|x| Box::new(x) as Box<SomeTrait>),
_ => Bar::new().map(|x| Box::new(x) as Box<SomeTrait>)
};
b.unwrap().do_something();
}
此处的更改位于对map
的调用中。而不是Box::new
,|x| Box::new(x) as Box<SomeTrait>
。首先创建一个具体类型的Box,然后将其转换为更通用的Box类型。
我认为如果我通过.map(Into::into)
进行转换可能看起来更干净,但我不确定这种情况是否有所改善。我很惊讶地发现没有From
的默认实现可以向上转换Box
。我得到了以下工作,作为另一个alternative solution:
impl<'a, T> From<Box<T>> for Box<SomeTrait+'a>
where T : SomeTrait+'a
{
fn from(b: Box<T>) -> Box<SomeTrait+'a> {
b as Box<SomeTrait+'a>
}
}
fn main () {
let b: Option<Box<SomeTrait>> = match "x" {
"x" => Foo::new().map(Box::new).map(Into::into),
_ => Bar::new().map(Box::new).map(Into::into)
};
b.unwrap().do_something();
}