user1496062
user1496062

Reputation: 1317

Static method in trait dynamic dispatch

Trying to get dynamic dispatch working in a trait static method but get a type must be known error.

I'm trying to achieve something like

Is the only way to make the trait generic?

pub struct Aggregate<T: AggregateRoot>
{
    pub id: Uuid,
    agg: T,
    changes:  Vec<Box<Any>>
}

impl <T :AggregateRoot > Aggregate<T>
{
    fn GetUncomittedChanges(&self) -> Vec<Box<Any>> { self.changes}
    fn MarkChangesAsCommitted(&self)  { self.changes.drain(..);}
}


trait AggregateRoot
{
    fn new2() -> Self;  //should be private
    fn new(id: Uuid) -> Self;

    fn LoadsFromHistory(changes : Vec<Box<Any>> ) -> Self
         where Self: Sized
    {
        let newAgg  = AggregateRoot::new2 ();
        changes.iter().map( |e| newAgg.Apply(e) );
        newAgg.MarkChangesAsCommitted();
        newAgg
    }

    fn Apply<U: Any>(&self, arg: U) ;
    fn GetId(&self) -> Uuid;
}

currently trying but gives 2 params expected 1 supplied.

Upvotes: 0

Views: 2197

Answers (1)

Shepmaster
Shepmaster

Reputation: 430851

Let's start with issues in how you asked the question, in the hopes that you will be able to ask better questions in the future. The complete error you are getting is:

<anon>:27:37: 27:52 error: the type of this value must be known in this context
<anon>:27             changes.iter().map( |e| newAgg.Apply(e) );
                                              ^~~~~~~~~~~~~~~

Note that the compiler error message shows you exactly which bit of code is at fault. It's useful to include that error when asking a question.

You've also included extraneous detail. For example, GetUncomittedChanges, id and GetId are all unused in your example. When solving a problem, you should produce an MCVE. This helps you understand the problem better and also allows people helping you to look at less code which usually results in faster turnaround.


Your code has a number of problems, but let's start at the first error:

let newAgg  = AggregateRoot::new2 ();

This says "for any possible AggregateRoot, create a new one". Many concrete types can implement a trait (which is the point of traits), but the compiler needs to know how much space to allocate for a given instance. There might be a struct that takes 1 byte or 200 bytes; how much space needs to be allocated on the stack in this case?

To progress, you can use Self::new2 instead. That means to create a new instance of the current implementor.

The next error is

<anon>:20:16: 20:40 error: no method named `MarkChangesAsCommitted` found for type `Self` in the current scope
<anon>:20         newAgg.MarkChangesAsCommitted();
                         ^~~~~~~~~~~~~~~~~~~~~~~~

You are calling a method on a concrete type from a trait implementation; this simply doesn't make any sense. What would happen if a bool implements this trait? It doesn't have a MarkChangesAsCommitted method. I don't know what you intended in this case, so I'll just delete it.

Now you get this error:

<anon>:19:9: 19:16 error: `changes` does not live long enough
<anon>:19         changes.iter().map( |e| newAgg.Apply(e) );
                  ^~~~~~~

note: reference must be valid for the static lifetime...
<anon>:17:5: 21:6 note: ...but borrowed value is only valid for the scope of parameters for function at 17:4

That's because your method Apply expects to be given a type that implements Any. However, you are passing a &Box<Any>. Any has a lifetime bound of 'static, and that reference is not static. A straightforward change is to accept a reference to a type that implements Any:

fn Apply<U: Any>(&self, arg: &U);

Now that the code compiles, there's a number of stylistic issues to fix:

  1. no space before :
  2. no space after >
  3. no space before (
  4. no space inside ()
  5. map should not be used for side effects
  6. function and variable names are camel_case
  7. most of the time, accept a &[T] instead of a Vec<T> as a function argument.
  8. use "Egyptian" braces, except when you are using a where clause.

All together, your code looks like:

use std::any::Any;

struct Aggregate<T: AggregateRoot> {
    agg: T,
    changes: Vec<Box<Any>>
}

impl<T: AggregateRoot> Aggregate<T> {
    fn mark_changes_as_committed(&self) { }
}

trait AggregateRoot {
    fn new() -> Self;

    fn load_from_history(changes: &[Box<Any>]) -> Self
         where Self: Sized
    {
        let new_agg = Self::new();
        for change in changes { new_agg.apply(change) }
        new_agg
    }

    fn apply<U: Any>(&self, arg: &U);
}

fn main() {}

Is there a way to constrain the concrete types of the AggregateRoot to Aggregates so mark_changes can be called?

Not that I'm aware of. It sounds like you want to move mark_changes to the trait and force all implementors of the trait to implement it:

trait AggregateRoot {
    fn load_from_history(changes: &[Box<Any>]) -> Self
         where Self: Sized
    {
        let new_agg = Self::new();
        for change in changes { new_agg.apply(change) }
        new_agg.mark_changes_as_committed();
        new_agg
    }

    fn mark_changes_as_committed(&self);

    // ...
}

Upvotes: 1

Related Questions