Implement a trait for another generic trait

Consider the following code :

pub trait TextureWithDefiniteVoxelType<TVoxel: Voxel> {
    fn get_dimension_count(&self) -> usize;
    fn get_voxel(&self, index: &[usize]) -> Result<&TVoxel, IndexOutOfRangeError>;
    fn get_voxels(&self) -> &Vec<TVoxel>;
    fn set_voxel(&mut self, index: &[usize], voxel: TVoxel) -> Result<(), IndexOutOfRangeError>;
    fn set_voxels(&mut self, voxels: &[TVoxel]) -> Result<(), SizeMismatchError>;
}

impl<T, TVoxel: Voxel> Index<&[usize]> for T
where
    T: TextureWithDefiniteVoxelType<TVoxel>,
{
    type Output = TVoxel;
    fn index(&self, index: &[usize]) -> &Self::Output {
        match TextureWithDefiniteVoxelType::<TVoxel>::get_voxel(self, index) {
            Ok(v) => return v,
            Err(e) => panic!("{}", e),
        }
    }
}

I get the following error on impl line :
the type parameter 'TVoxel' is not constrained by the impl trait, self type, or predicates unconstrained type parameter
I have 2 questions :

  1. What is the correct way to do this ? Do I have to use a macro and call it for every single implementation of this trait ? Or is there a better way ?
  2. What is the reasoning behind this error ? Is this a feature that's not implemented yet ? Or is there a reason why this is not allowed (and not planned to be allowed) ?

No, not a missing feature. Allowing this would be wrong because the implemented trait (Index) itself doesn't depend on TVoxel, so this would allow the exact same trait to be implemented multiple times for the same type.

Edit: This won't work due to orphan rules, unfortunately: Index<&[usize]> might already be implemented for some types that also implement TextureWithDefiniteVoxelType.

I think something like this should work, as it restricts each type to only having a single implementation of TextureWithDefiniteVoxelType:

pub trait TextureWithDefiniteVoxelType {
    type TVoxel: Voxel;
    fn get_dimension_count(&self) -> usize;
    fn get_voxel(&self, index: &[usize]) -> Result<&Self::TVoxel, IndexOutOfRangeError>;
    fn get_voxels(&self) -> &Vec<Self::TVoxel>;
    fn set_voxel(&mut self, index: &[usize], voxel: Self::TVoxel) -> Result<(), IndexOutOfRangeError>;
    fn set_voxels(&mut self, voxels: &[Self::TVoxel]) -> Result<(), SizeMismatchError>;
}

impl<T> Index<&[usize]> for T
where
    T: TextureWithDefiniteVoxelType,
{
    type Output = T::TVoxel;
    fn index(&self, index: &[usize]) -> &Self::Output {
        match TextureWithDefiniteVoxelType::get_voxel(self, index) {
            Ok(v) => return v,
            Err(e) => panic!("{}", e),
        }
    }
}

Indeed – Demo.

1 Like

So, Is Specialization feature in future releases going to fix this problem ?
Also, What is the correct way to do this right now, other than making index a subtype of the trait and implementing it for every single concrete type ?

I don't think this is a specialization issue. This seems to be much more deeply-rooted logically – multiple impls of the same trait, for the same type aren't allowed, not even with specialization (as they shouldn't be).

Without knowing in detail whether you actually need TextureWithDefiniteVoxelType to be generic, I can't offer much help, but if you don't, then you can reorganize the code so that it isn't, and it has an associated type instead. Furthermore, to overcome the orphan rule problem, you can define an indexing newtype, like this.

Actually no, this is just a practice code for me trying to understand the mindset required to code in rust.
So yeah it's not something that important to be solved, but I'm just interested to know how this problem is generally solved, say, for future problems that might occur.
So yeah, associated type is one way. But let's say it can't be implemented with an associated type.
Are there (Generally) other ways around this ?

Associated type is a fairly straightforward workaround. I never had the need to do anything else but change the type parameter to an associated type. I can't imagine, if you control the code, why that can't be done, but even if it can't, I don't know of a better way that I would recommend. Anything else will basically end up being more complicated.

2 Likes

That fairly answers my question.
Tbh I think I'm more bounded by OCD here than anything else :grimacing:

It's a bit out of topic to ask the following question, but just in case someone else might go down the path I just went; here's what I actually wanted to do :
I have different voxel types, and a generic struct called Texture that takes different generic arguments declaring its Voxel type and its Dimension count. So e.g. Texture<Voxel3xU8, 2> can be a simple RGB24 image.
Here's the question : What is the most idiomatic way in rust, to have a function that loads arbitrary types of textures (With voxel type or dimension count not known at compile time) from files, Without losing the ability to work with the generic struct as usual.
I presume an object-safe trait is required for this, but then I need to be able to downcast it back to concrete types where required, which is where I'm a bit confused.

I would definitely keep Texture generic over the voxel type – after all, it's basically just a glorified N-dimensional array.

Well, since Rust is statically-typed, there is no way, ultimately, to work with unknown types. Since it's not possible to summon types from thin air at runtime, you can't implement a function that e.g. reads data of any type at all from disk and then returns it behind a trait object.

What you can do is create an enum TextureDyn, with all the supported texture/voxel types, and create a self-describing format that includes a description of the dimensionality and voxel type of the image. Then you can match on the enum in order to get back to a concrete Texture<Voxel, DIM> type.

I'm not sure how exactly you are planning to use any "dynamically-typed" texture loaded from file without knowing what its type is, but the enum approach allows you to inspect the concrete type at runtime and use it subsequently.

2 Likes

I think I was just trying to run away from the fact that I have to write an enum including all those types and matching it against every single concrete type manually; but I guess that's the best way to go.

I'm not sure how exactly you are planning to use any "dynamically-typed" texture loaded from file without knowing what its type is

By passing it around and then inspecting it when needed, as you mentioned. Let's say for a library that loads and saves files containing those textures, where their type and dimension count is written in the file.

Thanks for the help.
I'll give macros a shot for the enum case to define required common methods for the dynamically typed texture enum.

I agree that it sounds like you should just be using an enum.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.