Is there a solution to avoid this circular generic


#1

I’m implementing this struct:

pub struct ZComponent(
    pub u8,
);

impl Component for ZComponent {
    type Storage = VecStorage<Self>;
}

However I would like to make the ‘VecStorage’ generic, so a different storage type could be used, ex:

pub struct ZComponent<S>(
    pub u8,
    PhantomData<S>
);

impl<S> Component for ZComponent<S>
    where S: UnprotectedStorage<Self> + Any + Send + Sync {
    type Storage = S;
}

The issue is that now I can’t instantiate it, because the Component generic require the Storage, and the Storage generic requires the Component, so trying to instantiate it is like this ZComponent<VecStorage<ZComponent<VecStorage<.....>>>>

Any suggestions?


#2

My first instinct is that the design of components might be overly coupled to the storage context if it needs to know how it is being stored.

Assuming the coupling is unavoidable, what you might want to do is split the component into a ‘dumb’ version and a stored version. So, you have VecStorage<ZComponent> and StoredZComponent<VecStorage<ZComponent>>.

You could probably do this if you only needed to provide a type constructor each way, so you could have ZComponent<VecStorage> and VecStorage<ZComponent>, but the language doesn’t support this without hackish workarounds at the moment.


#3

Perhaps Component can be generic and ZComponent not? It looks like you’re making ZComponent generic just so it’s a carrier of the generic type for use by its Component impl. So if Component becomes generic (rather than having an associated type), you can implement it multiple times for ZComponent with different storage types being the generic params of Component.

But it’s hard to say because the bigger picture is unclear (eg how you plan to use Component and ZComponent).


#4

This works if a component is always accessed via its storage. I was working with the assumption that you’d need to have components floating around in the wild that “know” how they’re stored, in which case, the storage method needs to be part of the type.

You could blend both approaches.

struct VecStorage<C> ...
struct VecStored<C> ...

impl<C: Component> StoredComponent for VecStored<C> ...

impl<S: Storage> Component<S> for ZComponent ...

The benefit of this is that the storage is always able to construct the stored version of its component.

Though, if knowing the correct storage is safety critical in the ZComponent code, then it will need to be part of the type.


#5

Component isn’t defined by me, its from an external crate (specs), so I can’t modified its definition, and I don’t think a dumb version would be viable either.


#6

Okay. Remember how I mentioned a hack? You could do

struct ZComponent<S: StorageMode<ZComponent<S>>> ...

struct VecStored;
impl<C: Component> StorageMode<C> for VecStored {
    type Storage = VecStorage<C>;
}

impl<S> Component for ZComponent<S>
where
    S: StorageMode<Self>,
{
    type Storage = <S as StorageMode<Self>>::Storage;
}

With generic associated types (which are planned but not implemented), this would be

struct ZComponent<S: StorageMode> ...

struct VecStored;
impl StorageMode for VecStored {
    type Storage<C: Component> = VecStorage<C>;
}

impl<S> Component for ZComponent<S>
where
    S: StorageMode,
{
    type Storage = S::Storage<Self>;
}

#7

Ok, I see. Then I’d probably look into the same approach that @stevenblenkinsop suggested: define your own trait and marker types to denote the type of storage.


#8

@stevenblenkinsop I gave your hack a try, but I’m getting the error

error[E0275]: overflow evaluating the requirement `ZComponent<VecStored>: Component`
  --> src\chess\mod.rs:48:15
   |
48 |         world.register::<ZComponent<VecStored>>();
   |               ^^^^^^^^
   |
   = note: required because of the requirements on the impl of `StorageMode<ZComponent<
VecStored>>` for `VecStored`

This is the code I have for the ZComponent

pub trait StorageMode<C>
where C: Component + Sync {
    type Storage: UnprotectedStorage<C> + Any + Send + Sync;
}

pub struct VecStored;
impl<C> StorageMode<C> for VecStored
where C: Component + Send + Sync {
    type Storage = VecStorage<C>;
}

pub struct ZComponent<S: StorageMode<ZComponent<S>> + Sync + Send + 'static> (
    pub u8,
    PhantomData<S>
);

impl<S> Component for ZComponent<S>
where S: StorageMode<Self> + Sync + Send + 'static
{
    type Storage = <S as StorageMode<Self>>::Storage;
}

#9

You should drop some of those C: Component requirements - they’re not strictly necessary for ZComponent<S> to impl Component. For example:

pub trait StorageMode<C> where C: Sync {
    type Storage: UnprotectedStorage<C> + Any + Send + Sync;
}

pub struct VecStored;

impl<C> StorageMode<C> for VecStored
    where C: Send + Sync + 'static {
    type Storage = VecStorage<C>;
}

pub struct ZComponent<S> (
    pub u8,
    PhantomData<S>,
);

impl<S> Component for ZComponent<S>
    where S: StorageMode<Self> + Sync + Send + 'static
{
    type Storage = S::Storage;
}

#10

Yay, it compiles now. Thank you both, I figured this was a long shot and that it wouldn’t actually be possible to do :grin:.


#11

Good thing the specs library doesn’t over-specify its interface (by bounding VecStorage et al.) the way I was doing, or there’d be real pickle :sweat_smile:.


#12

Yes, indeed! :wink: