Satisfying Default for trait object struct members


#1

I have a struct that I’m deriving Default on, but one of the members, listener, is a trait object.

/// A thread-base queue implementation
#[derive(Default)]
pub struct ThreadQueue<'a> {
    buffer: VecDeque<Callback>,
    state: State,
    listener: &'a (QueueListener + 'a),
}

So this won’t compile because listener isn’t a struct on which I can implement Default.

What options do I have here to get this to compile? Looking at it, I could probably ditch the derive Default, but what if the situation really required it?


#2

Does listener really need to be a trait object?

You can impl Default yourself but then you’d need to know how to construct the trait object. What is the default state that you’d like? If you have a reference to a trait object then you pretty much can’t default initialize since you need a reference given to you from outside.


#3

Interesting questions. QueueListener is a delegate passed in by the context that is using the Queue, so it can receive callbacks for events. Sorry I should have copied in the code.

pub trait QueueListener<'a> {
    fn queue_did_become_empty(&self, queue: &'a (Queue + 'a));
    fn queue_did_become_populated(&self, queue: &'a (Queue + 'a));
    fn queue_did_move_from_state(&self, queue: &'a (Queue + 'a))
    fn queue_did_move_to_state(&self, queue: &'a (Queue + 'a));
}

I decided to define a DeafListener that does nothing as default because providing a listener delegate isn’t mandatory.

I guess I’ll have to remove the derive Default


#4

Any reason not to have:

pub struct ThreadQueue<L: QueueListener> {
    buffer: VecDeque<Callback>,
    state: State,
    listener: L,
}

pub trait QueueListener {
    fn queue_did_become_empty(&self, queue: &Queue);
    fn queue_did_become_populated(&self, queue: &Queue);
    fn queue_did_move_from_state(&self, queue: &Queue)
    fn queue_did_move_to_state(&self, queue: &Queue);
}

You can consider making QueueListener generic as well:

pub trait QueueListener<Q: Queue> {
    fn queue_did_become_empty(&self, queue: &Q);
...
}

#5

That’s an interesting idea. I could also implement the deaf listener on the queue object directly. Thanks Vitaly! :slight_smile:


#6

If you go with generic listener trait, I’d actually make the methods generic rather than the trait itself being generic.

trait QueueListener {
  fn on_empty<Q: Queue>(&self, q: &Q);
  ...
}

But the best approach depends on how the rest of the code will be structured so you may want to play with different designs.


#7

Yeah, I was about to say exactly that. I’m super new to Rust, so I’ll probably keep what I can simple and deal with the complexity that’s not optional. Thanks again for your help.


#8

Here is a simple contrived sketch (I’m on mobile so it’s terse too :slight_smile:) of how you may use a generic listener trait (rather than generic methods).

Ok that’s all from me for now. Happy to help if you have other questions or want idea bounces.


#9

Thanks! :smiley:


#10

You could wrap the borrow in an std::option::Option. The default implementation for Option is None.


#11

Yup, you can. The added advantage of ThreadQueue<DeafListener> is compiler will statically see that DeafListener has empty methods and will remove calls to them. With a None, it won’t statically know (modulo trivial cases) that the Option is always that value since this isn’t encoded in the types.