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?
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.
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
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);
...
}
1 Like
That's an interesting idea. I could also implement the deaf listener on the queue object directly. Thanks Vitaly! 
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.
1 Like
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.
1 Like
Here is a simple contrived sketch (I’m on mobile so it’s terse too
) 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.
1 Like
You could wrap the borrow in an std::option::Option. The default implementation for Option is None.
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.