2016-06-26 68 views
0
trait Foo {} 
trait Bar: Foo {} 

struct Zxc; 
impl Foo for Zxc {} 

struct Baz; 
impl Bar for Baz {} 
impl Foo for Baz {}  

struct Abc<F: Foo> { 
    f: F 
} 
impl<F: Foo> Abc<F> { 
    fn bared<B: Bar>(&mut self, b: B) { 
     self.f = b; 
    } 
} 

fn main() { 
    let mut abc = Abc { f: Zxc }; 
    abc.bared(Baz); 
} 

Try it on the playground中保存`trait Bar:Foo {}`。在`struct Abc'

Abc存储Foo特征; abc.bared(Baz)采取Baz,实现FooBar,但在Abc中保存Baz时存在类型不匹配错误。如何解决它?

回答

2

ZxcBaz是不相关的类型,您不能将其中一个指派给另一个。

如果你想Abc能够储存他们都使用“基础类” Foo,使用trait object,例如

struct Abc { 
    f: Box<Foo> 
} 
//^Abc is not a template. 

impl Abc { 
    fn bared<B: Bar + 'static>(&mut self, b: B) { 
     self.f = Box::new(b); 
    } 
    //^maybe you want to change it to take `b: Box<Bar>` 
    // it depends on how you want to expose the API. 
} 

fn main() { 
    let mut abc = Abc { f: Box::new(Zxc) }; 
    abc.bared(Baz); 
} 

但是,Rust的OOP范例与Java的范例不同,特征对象可能不是最好的解决方案。也许你应该展示你想解决的实际问题。

+0

可能像使用'unsafe'? – Lupe

+2

我会100%推荐不要在这里使用'unsafe'。我不相信这是必要的,使用它很可能会导致内存错误。 – Shepmaster

+0

@Shepmaster拐杖创造漂亮的API。 – Lupe

1

你还没有申报可以存储任何实施Foo;你已经声明了一种制造类型的工厂,可以存储任何实现Foo类型的特定对象。

通过一些代码的状况:

struct Abc<F: Foo> { 
    f: F 
} 

这大致翻译为“给我一个类型F它实现Foo,我将创建一个类型Abc<F>存储一个”。

当你使用它:

let mut abc = Abc { f: Box::new(Zxc) }; 

添加回由编译器推断类型:

let mut abc: Abc<Zxc> = Abc { f: Box::new(Zxc) }; 

所以abc.f类型是Box<Zxc> - Box<Foo>

所以,现在你有一个Abc<Zxc> - 不是一般的Abc(直到您指定的类型参数F您不能创建一个具体的对象)。

现在应该清楚为什么你不能使用它与Baz

现在获得实际的错误:

<anon>:17:18: 17:19 error: mismatched types: expected `F`, 
    found `B` (expected type parameter, 
    found a different type parameter) [E0308] <anon>:17   self.f = b; 
         ^<anon>:17:18: 17:19 help: see the detailed explanation for E0308 error: aborting due to previous error 

的错误是不实际的调用abc.bared;它的定义是:

// In the impl of Abc<F> 
fn bared<B: Bar + 'static>(&mut self, b: B) { 
    self.f = Box::new(b); 
} 

这个方法说,它可以将任何类型B,它实现Bar但可能不会在所有涉及到F,并将其存储在self.f,这是Box<F>型。您不能将Box<B>指定为Box<F>,因为它们是不同的类型。

正如@kennytm所说的,您可以通过将该字段设置为特性对象(Box<Foo>)来存储不同的类型,该特性对象与其他语言中的基类指针更类似。

0

除了其他的答案,如果你正试图构建生成器模式,你可能想改变你的bared方法,通过值取建设者,然后返回一个新的类型:

impl<F: Foo> Abc<F> { 
    fn bared<B: Bar>(self, b: B) -> Abc<B> { 
     Abc { f: b } 
    } 
} 

这改变从Abc<Zxc>Abc<Baz>在调用bared具体类型:

let abc = Abc { f: Zxc }; 
let def = abc.bared(Baz); 
+0

'Abc '的大小为0字节,这是否意味着编译器优化了这样的构建器并且内置了'Abc '来代替调用? – Lupe

+0

@Lupe [我对此印象非常深刻](https://twitter.com/JakeGoulding/status/738568045623431168),内嵌的级别表明编译器已经完成了我之前创建的构建器。 – Shepmaster

+0

编写'fn foo(mut self) - > Self'是否有意义,从而向用户显示需要更改? – Lupe