1
votes

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?

1
Why does your internal logic require Self: Sized on save_resource? Why would you possibly want to prevent methods being called on trait objects, when you at the same time want to call a method on trait object (self.store is a trait object, and you call save_resource() on it).user4815162342
Your first issue can be fixed by replacing a default implementation with a blanket implementation. Does that help?user4815162342
@user4815162342 Thanks for the reply! The Self: Sized is required because it calls self.create_resource(), which in turn requires sized. I'll update the question. The blanket implementation won't work, because various implementations of the Trait must be possible that have different logic.joepio
that share a lot of logic — create a new type that holds the shared logic and is parameterized by one (or more) generic pieces that specify the unique behavior. Composition over inheritance.Shepmaster
Maybe it would help if you trimmed your code to a minimal example that still shows the issue - two separate impls and all that, but with unnecessary stuff removed (like actually working with the files). Otherwise we risk solving the various "ys" of an xy problem (such as how I eliminated your Self: Sized restriction) without getting to the bottom of it.user4815162342

1 Answers

0
votes

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) {
        //
    }
}