No cost abstraction

Why I am not able to monomorphise a trait in a struct member

trait Callback{
    fn on_recv(&self, msg: &[u8]);
}
struct LoggerCallback;

impl Callback for LoggerCallback{
    fn on_recv(&self, msg: &[u8]){
        println!("LoggerCallback: {:?}", msg);
    }
}

struct Clt2;
impl Clt2{
    fn new(callback: impl Callback) -> Self{  // --> WORKS
        println!("Clt2::new");
        Self{}
    }
}

struct Clt{
    callback: impl Callback, // --> DOES NOT WORK
}

fn main(){
    println!("Hello, world!");
}

What do you expect it to do? In a global context, there's nothing to infer the type from, so this doesn't make much sense.

2 Likes

Did you mean

struct Clt<T: Callback> {
  callback: T,
}

?

2 Likes

I would like to be able to create different implementations of the Callback trait and pass it into the Clt so a call back can for example just log or just capture msg into a memory map, etc. Here is more to complete this example

trait Callback{
    fn on_recv(&self, msg: &[u8]);
}
struct LoggerCallback;

impl Callback for LoggerCallback{
    fn on_recv(&self, msg: &[u8]){
        println!("LoggerCallback: {:?}", msg);
    }
}

struct Clt2;
impl Clt2{
    fn new(callback: impl Callback) -> Self{  // --> WORKS
        println!("Clt2::new");
        Self{}
    }
}

struct Clt{
    callback: impl Callback, // --> DOES NOT WORK
}
impl Clt{
    fn new(callback: impl Callback) -> Self{
        Self{ callback }
    }
    fn send(&self, msg: &[u8]){
        self.callback.on_recv(msg);
    }
}

struct Logger{

}
impl Logger{
    fn new() -> Self{
        Self{}
    }
}
impl Callback for Logger{
    fn on_recv(&self, msg: &[u8]){
        println!("Logger: {:?}", msg);
    }
}

struct Cacher<'a>{
    store: Vec<&'a [u8]>
}

impl<'a> Cacher<'a>{
    fn new() -> Self{
        Self{ store : Vec::new()}
    }
}
impl<'a> Callback for Cacher<'a>{
    fn on_recv(&self, msg: &[u8]){
        self.store.push(msg);
    }
}

fn main(){
    let logger = Logger::new();
    let clt = Clt::new(logger);
    clt.send(&b"to the log"[..]);

    // vs

    let cacher = Cacher::new();
    let clt = Clt::new(cacher);
    clt.send(&b"to the store"[..]);

    println!("Hello, world!");
}

The problem with this approach is it passes a type but I need an instance of that type

If you don't want to parameterize your struct with a generic type, you'll need to either pick a specific implementing type or type erase implementing types.

You can erase implementing types with dyn Callback + '_. Here's one possible way, wherein the implementing type must also be 'static:

trait Callback {
    fn on_recv(&self, msg: &[u8]);
}

struct Clt {
    callback: Box<dyn Callback>,
}

impl Clt {
    fn new(callback: impl Callback + 'static) -> Self {
        let callback = Box::new(callback);
        Self { callback }
    }
}

N.b. there were some unrelated borrower errors around Cacher<'_> which I have ignored in this comment [1].


Note that this:

impl Clt {
    fn new(callback: impl Callback + 'static) -> Self {

Is a (arguably slightly) less capable version of this:

impl Clt {
    fn new<C: Callback + 'static>(callback: C) -> Self {

which is to say, it introduces a generic type parameter to the function. So the more direct translation for Ctl is indeed to add a generic type parameter to the struct.

Syntactically hiding the fact that a struct is generic, not being able to fully name the type with its concrete parameters, etc. would be a lot more problematic for structs than it is for fns.


  1. but will probably give you grief within your current design â†Šī¸Ž

4 Likes

Thanks really, great suggestion but I am specifically trying to avoid dynamic dispatch in my case because the callback inside Clt struct is in the hot path.

Generics don't perform dynamic dispatch. You are confusing generics with trait objects.

3 Likes