推广两个结构

时间:2016-08-29 18:59:51

标签: generics types rust

我有两个独立的库(不同的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 {})
    }
}

我现在想以相同的方式处理这两个结构,而不管我是否真的有FooBar

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进行投射,但由于这些都是非标量类型,因此无法正常工作。

如何解决此问题(不涉及FooBar的实施)?我觉得我试图将经典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();
}

2 个答案:

答案 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();
}