8
votes

I have a trait in which I want to provide a method. The method is to be implemented in terms of some helpers that have no business being inside the trait and are non-trivial enough that dynamic polymorphism makes more sense than making them generic. So I have code along the lines of

fn use_trait(x: &Trait) {
    println!("object says {}", x.needed());
}

trait Trait {
    fn needed(&self) -> &str;

    fn provided(&self) {
        use_trait(self);
    }
}

struct Struct();

impl Trait for Struct {
    fn needed(&self) -> &str {
        "Hello, world!"
    }
}

fn main() {
    Struct().provided();
}

Which, however, does not compile, with error:

error[E0277]: the trait bound `Self: std::marker::Sized` is not satisfied
 --> <anon>:9:19
  |
9 |         use_trait(self);
  |                   ^^^^ the trait `std::marker::Sized` is not implemented for `Self`
  |
  = help: consider adding a `where Self: std::marker::Sized` bound
  = note: required for the cast to the object type `Trait`

I understand why—it is not guaranteed somebody won't implement the trait for an unsized type (converting from &T where T: Trait to &Trait requires T: Sized, but the declaration does not require that).

However, the advice will not do what I need. I can add

fn needed(&self) -> &str where Self: Sized

but then the needed() method won't be accessible on &Trait (because Trait : ?Sized), which renders the thing useless, because the type (the actual one that does something useful) is always handled as Arc<Trait>. And adding

trait Trait: Sized

is even worse, because that does not permit &Trait at all (Trait as a type is unsized, so Trait type does not implement trait Trait).

Of course I can simply make

fn use_trait<T: Trait>(x: &T)

but there is a lot behind it in the real code, so I don't want monomorphisation there especially since the trait is otherwise always handled as trait object.

Is there any way to tell Rust that all types that impl Trait must be sized and here is a definition of a method that should work for all of them?

2

2 Answers

2
votes

You need an additional as_trait function on Trait and its implementations:

trait Trait {
    fn needed(&self) -> &str;

    fn provided(&self) {
        use_trait(self.as_trait());
    }

    fn as_trait(&self) -> &Trait;
}

struct Struct();

impl Trait for Struct {
    fn needed(&self) -> &str {
        "Hello, world!"
    }

    fn as_trait(&self) -> &Trait {
        self as &Trait
    }
}

You can try it on the playground. (trait objects)

1
votes

Enhanced version of @JoshuaEntrekin's answer:

The helper as_trait function can be put in an auxiliary trait that gets blanket implementation for all Sized types trying to implement Trait. Then the implementer of Trait does not have to do anything special and the conversion works.

fn use_trait(x: &Trait) {
    println!("object says {}", x.needed());
}

trait Trait : AsTrait {
    fn needed(&self) -> &str;

    fn provided(&self) where Self : AsTrait {
        use_trait(self.as_trait());
    }
}

trait AsTrait {
    fn as_trait(&self) -> &Trait;
}

impl<T : Trait + Sized> AsTrait for T {
    fn as_trait(&self) -> &Trait { self }
}

struct Struct();

impl Trait for Struct {
    fn needed(&self) -> &str {
        "Hello, world!"
    }
}

fn main() {
    Struct().provided();
}

(on play).

It would also be possible to simply put provided in the auxiliary trait, but then it would have to dynamically dispatch to the other methods of Self unnecessarily.


Update: Actually, the point is that it should still be possible to override provided.

Now the above can be improved further by making it generic. There is std::makrer::Unsize, which is unstable at the time of this writing. We can't make

trait Trait : Unsize<Trait>

because Rust does not allow CRTP, but fortunately it is enough to put the constraint on the method. So

fn use_trait(x: &Trait) {
    println!("object says {}", x.needed());
}

trait Trait {
    fn needed(&self) -> &str;

    fn provided(&self) where Self: AsObj<Trait> {
        use_trait(self.as_obj());
    }
}

trait AsObj<Tr: ?Sized> {
    fn as_obj(&self) -> &Trait;
}

// For &'a Type for Sized Type
impl<Type: Trait> AsObj<Trait> for Type {
    fn as_obj(&self) -> &Trait { self }
}

// For trait objects
impl AsObj<Trait> for Trait {
    fn as_obj(&self) -> &Trait { self }
}

struct Struct();

impl Trait for Struct {
    fn needed(&self) -> &str {
        "Hello, world!"
    }
    
    fn provided(&self) {
        println!("Aber dieses Objekt sagt Grüß Gott, Welt!"); // pardon my German, it is rusty.
    }
}

fn main() {
    let s: &Trait = &Struct();
    s.provided();
}

(on play)

This finally makes it transparent for the implementors of other versions.

See also this users thread.