Need some help with TraitObjects


#1

So, the gist of what I want to do is this:

https://play.rust-lang.org/?gist=f9075fadae6659f0ab35744d2be84fbe&version=nightly&backtrace=0

More or less I’m trying to allow client code to write a default implementation of the function in A and make it be callable in B. Of course B::new_b() could receive a function as a parameter so the client could write a closure, but I don’t find it ergonomic (but if there is no other alternative, so be it).

Any alternative way of solving this is welcome.

But maybe I’ve been reading too much Modern C++ Design when no one is looking…


#2

You don’t have any trait objects here, only “regular” traits that generate different versions of the code instead of doing dynamic dispatch.

struct<T: A> and impl<T> make it static dispatch.

Box<A> would be a trait object. And to have the dynamic dispatch you need to make an actual object, because it has to store the vtable somewhere.


#3

Structs have an implied Sized requirement, which can be overriden by adding ?Sized:

struct B<T: A + ?Sized>

…but even then I don’t know how to make Rust accept A1 trait as the type.

But this works:

use std::marker::PhantomData;

struct B<T: A> {
    _pd: PhantomData<T>,
}

trait A {
    fn a(param: &B<Self>) where Self: Sized;
}

struct A1;
impl A for A1 {
    fn a(param: &B<Self>) {
        println!("Called from A1");
    }
}

impl<T> B<T>
    where T: A
{
    fn new_b() -> B<T> {
        B { _pd: PhantomData }
    }

    fn call_a(&self) {
        T::a(self);
    }
}

fn main() {
    let b = B::<A1>::new_b();
    b.call_a();
}

#4

I’m feeling really silly for not trying that…

Anyway, is there a more idiomatic way of doing what I’m doing?


#5

Unless I misunderstood, it seems you’d require your callers to impl A so that you can then call it? If so, I’d find that less ergonomic than letting them pass a closure. What am I missing?


#6

It’s mostly because the code of each closure would be extensive in the real code.

Also, most client code will probably use one of the ~6 common implementations that I can supply beforehand.

Sure, I can supply these implementations as functions and client code just pass the functions around…

I’m still not 100% sure what is the best way of doing it.


#7

So if traits need to be impl’d just for the purpose of calling a function (i.e. they don’t have any other purpose in your lib), I think using functions/closures is more straightforward. Callers can use your canned versions or write a custom one.

Also, your original code has trait A and A1: A, with A1 providing a default impl. Just wanted to point out, in case this was lost, that your callers will need to impl A as well if they decide to impl A1 - there’s no inheritance/type hierarchy there. Then you end up with an impl that requires UFCS to resolve which “a” you want to call, and it’s confusing for the caller as well I think.


#8

Thanks. Yeah, I guess you’re right. I’ll do some refactoring now before the code grows.