joepio
joepio

Reputation: 4484

How do I deal with Sized requirements in a trait object?

I'm having trouble understanding traits and object safety in Rust.

I have a StoreTrait for storing some data and a Resource struct that holds a reference to a StoreTrait.

I want the Resource to have a reference to a store intance, because many of the methods of Resource will use store, and I don't want to explicitly pass store to every method on Resource.

I also need to have the logic reside in the trait, because I have various impls that will need to share it (an in-memory and an on-disk store). So moving it into the impl is not what I'd prefer.

In the Store trait, I try passing &Self to a function, but it fails because &Self is not Sized:

pub trait StoreTrait {
    fn create_resource(&self) {
        let agent = Resource::new(self);
    }
}

struct Resource<'a> {
    store: &'a dyn StoreTrait,
}

impl<'a> Resource<'a> {
    pub fn new(store: &dyn StoreTrait) -> Resource {
        Resource { store }
    }
}
error[E0277]: the size for values of type `Self` cannot be known at compilation time
 --> src/lib.rs:3:35
  |
3 |         let agent = Resource::new(self);
  |                                   ^^^^ doesn't have a size known at compile-time
  |
  = note: required for the cast to the object type `dyn StoreTrait`
help: consider further restricting `Self`
  |
2 |     fn create_resource(&self) where Self: Sized {
  |                               ^^^^^^^^^^^^^^^^^

This is where this might become an XY problem

The compiler suggests using where Self: Sized bounds in these methods. However, this causes another problem later when calling save_resource() from a Resource, since that means I'm invoking a method on a trait object with a Sized bound.

pub trait StoreTrait {
    // So after adding the trait bounds here...
    fn create_resource(&self)
    where
        Self: Sized,
    {
        let agent = Resource::new(self);
    }

    // And here (internal logic requires that)...
    fn save_resource(&self, resource: Resource)
    where
        Self: Sized,
    {
        // This now requires `Self: Sized`, too!
        self.create_resource()
    }
}

pub struct Resource<'a> {
    pub store: &'a dyn StoreTrait,
}

impl<'a> Resource<'a> {
    pub fn new(store: &dyn StoreTrait) -> Resource {
        Resource { store }
    }

    pub fn save(&self) {
        self.store.save_resource(self)
    }
}

playground

error: the `save_resource` method cannot be invoked on a trait object
  --> src/lib.rs:26:20
   |
13 |         Self: Sized;
   |               ----- this has a `Sized` requirement
...
26 |         self.store.save_resource(self)
   |                    ^^^^^^^^^^^^^

How do I circumvent setting the trait bound? Or how do I prevent calling a method on a trait object? Perhaps I'm doing something else that doesn't make a ton of sense?

edit: I ended up changing the arguments for the functions. Whenever I used &dyn StoreTrait, I switched to &impl StoreTrait. This means the functions with that signature are compiled for every implementation, which makes the binary a bit bigger, but it now works with the sized requirement. yay!

Upvotes: 3

Views: 2770

Answers (1)

Bob Bobbio
Bob Bobbio

Reputation: 577

Perhaps if you just move function from the trait to each implementation it will do what you want?

fn main() {}

pub trait StoreTrait {
    fn create_resource(&self);

    fn save_resource(&self, resource: &Resource);
}

struct Resource<'a> {
    store: &'a dyn StoreTrait,
}

impl<'a> Resource<'a> {
    pub fn new(store: &dyn StoreTrait) -> Resource {
        Resource { store }
    }

    pub fn edit(&self) {
        self.store.save_resource(self)
    }
}

struct StoreMem {
    resources: Vec<String>,
}

impl StoreTrait for StoreMem {
    fn create_resource(&self) {
        let agent = Resource::new(self);
    }
    
    fn save_resource(&self, resource: &Resource) {
        //
    }
}

struct StoreDisk {
    resources: Vec<String>,
}

impl StoreTrait for StoreDisk {
    fn create_resource(&self) {
        let agent = Resource::new(self);
    }
    
    fn save_resource(&self, resource: &Resource) {
        //
    }
}

Upvotes: 0

Related Questions