Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rust generics/traits: "expected 'Foo<B>', found 'Foo<Foo2>'"

Tags:

rust

I asked a similar question earlier which has helped me understand what is going on under the hood but I still can not get Rust to do what I want it to do when it comes to generic programming. Here's some code:

struct Foo<B: Bar> { bars: Vec<Box<B>> }

struct Foo2;

trait Bar {}

impl Bar for Foo2 {}

impl<B: Bar> Foo<B> {
  fn do_something() -> Foo<B> {
    let foo2:Box<Bar> = box Foo2;
    let mut foo = Foo { bars: vec!(box Foo2) };
    foo.bars.push(box Foo2);
    foo // compiler: *ERROR*
  }
}

Error: expected 'Foo<B>', found 'Foo<Foo2>'

  1. How can I give the compiler a hint or explicitly tell the compiler that foo (Foo) implements Bar (B: Bar)?
  2. Is this a bug? Should I just hold off on using Rust until it reaches 1.0?

version: 0.12.0-nightly (4d69696ff 2014-09-24 20:35:52 +0000)


Problems I see with @Levans' solution:

struct Foo2;

struct Foo3 {
  a: int
}

trait Bar {
    fn create_bar() -> Self;
}

impl Bar for Foo2 {
    fn create_bar() -> Foo2 { Foo2 } // will work
}

impl Bar for Foo3 {
    fn create_bar(a: int) -> Foo3 { Foo3 {a: a} } // will not work
}

Error: method 'create_bar' has 1 parameter but the declaration in trait 'Bar::create_bar' has 0

Also, I noticed this: Bar::create_bar(). How would Rust know to use Foo2's implementation?

like image 928
goo Avatar asked Mar 19 '23 09:03

goo


1 Answers

When you define a function using <B: Bar> you're telling the compiler "you can replace in this function B by any type implementing the trait Bar".

For example, if you created a struct Foo3 implementing trait Bar as well, the compiler would expect to be able to call do_something with B being Foo3, which is not possible with your current implementation.

In your situation, your do_something function attempts to create a B object, it thus needs a generic way to do so, given by the Bar trait, as a create_bar() method for example, like this :

struct Foo<B: Bar> { bars: Vec<Box<B>> }

struct Foo2;

trait Bar {
    fn create_bar() -> Self;
}

impl Bar for Foo2 {
    fn create_bar() -> Foo2 { Foo2 }
}

impl<B: Bar> Foo<B> {
  fn do_something() -> Foo<B> {
    let mut foo = Foo { bars: vec!(box Bar::create_bar()) }; 
    foo.bars.push(box Bar::create_bar());
    foo 
  }
}

Answer to edit :

In your code, it indeed won't work because you expect to pass more arguments to create_bar, which is not possible as it does not respect the trait definition that create_bar does not take any arguments.

But something like this would work without any problem :

struct Foo2;

struct Foo3 {
  a: int
}

trait Bar {
    fn create_bar() -> Self;
}

impl Bar for Foo2 {
    fn create_bar() -> Foo2 { Foo2 }
}

impl Bar for Foo3 {
    fn create_bar() -> Foo3 { Foo3 {a: Ou} }
}

The point is : your do_something function cannot create Bar objects without a generic way of doing so, a way that would not depend on which type is in <B>, provided it implements Bar. That's how generics work : if you call do_something::<Foo2>(), it's exactly as if you replaced B by Foo2 in the whole definition of your function.

Yet, I suspect what you really are trying to do is store differents types, all implementing Bar in the same Vec, (otherwise wrapping a Box inside would be quite useless), you can achieve this with trait objects, and it does not require generics :

struct Foo<'a> { bars: Vec<Box<Bar + 'a>> }

struct Foo2;

trait Bar {}

impl Bar for Foo2 {}

impl<'a> Foo<'a> {
  fn do_something() -> Foo<'a> {
    let mut foo = Foo { bars: vec!(box Foo2 as Box<Bar>) };
    foo.bars.push(box Foo2 as Box<Bar>);
    foo
  }
}

Basically, Trait objects are references or pointers to objects, casted as Trait :

let foo2 = Foo2;
let bar = &foo2 as &Bar; // bar is a reference to a Trait object Bar

And as provided in my example, it works with Boxes as well.

like image 134
Levans Avatar answered Mar 29 '23 05:03

Levans