Spurious conflicting implementation error

I have a type that's a wrapper for a boxed slice, and am having some trouble implementing IndexMut for it:

struct Buffer;

struct CachedSlice<T> {
    cpu: Box<[T]>,
    gpu: Buffer,
    dirty: Option<Range<usize>>,
}

impl<T,I:SliceIndex<[T]>> Index<I> for CachedSlice<T> {
    type Output = I::Output;
    fn index(&self, idx:I)->&I::Output {
        &self.cpu[idx]
    }
}

I believe that this should work, because the compiler knows that the SliceIndex::Output associated types are distinct from each other, which means that the implementations cannot overlap in practice, i.e. no type I can implement SliceIndex<[T]> with two different output types, so only one of these impls will ever apply for any choice of the type parameters T and I:

impl<T,I:SliceIndex<[T], Output=[T]>> IndexMut<I> for CachedSlice<T> {
    fn index_mut(&mut self, idx:I)->&mut [T] {
        let slice = &mut self.cpu[idx];
        // Update self.dirty
        slice
    }
}

impl<T,I:SliceIndex<[T], Output=T>> IndexMut<I> for CachedSlice<T> {
    fn index_mut(&mut self, idx:I)->&mut T {
        let item = &mut self.cpu[idx];
        // Update self.dirty
        item
    }
}

Unfortunately, the compiler doesn't agree :frowning: . Other than a concrete implementation for every possible index type, is there a way to make this work?

I suspect the answer is "no."

You can do it with some helper trait, so the implementations of IndexMut are consolidated into a single one.

I don’t know where I’ve seen this but I did see this limitation before, I believe it’s a known problem.

Anyways… here‘s a demonstration of the workaround following your approach 1b:

use std::ops::{Index, IndexMut, Range};
use std::slice::SliceIndex;

struct Buffer;

struct CachedSlice<T> {
    cpu: Box<[T]>,
    gpu: Buffer,
    dirty: Option<Range<usize>>,
}

impl<T, I: SliceIndex<[T]>> Index<I> for CachedSlice<T> {
    type Output = I::Output;
    fn index(&self, idx: I) -> &I::Output {
        &self.cpu[idx]
    }
}

trait HelperTrait<T, I: SliceIndex<[T], Output = Self>> {
    fn index_mut_impl(this: &mut CachedSlice<T>, idx: I) -> &mut I::Output;
}
impl<T, I: SliceIndex<[T]>> IndexMut<I> for CachedSlice<T>
where
    I::Output: HelperTrait<T, I>,
{
    fn index_mut(&mut self, idx: I) -> &mut I::Output {
        I::Output::index_mut_impl(self, idx)
    }
}

impl<T, I: SliceIndex<[T], Output = [T]>> HelperTrait<T, I> for [T] {
    fn index_mut_impl(this: &mut CachedSlice<T>, idx: I) -> &mut [T] {
        let slice = &mut this.cpu[idx];
        // Update self.dirty
        slice
    }
}

impl<T, I: SliceIndex<[T], Output = T>> HelperTrait<T, I> for T {
    fn index_mut_impl(this: &mut CachedSlice<T>, idx: I) -> &mut T {
        let item = &mut this.cpu[idx];
        // Update self.dirty
        item
    }
}

Rust Playground

2 Likes

Also, happy 3 year anniversary on this forum :partying_face: I always love to see your posts here!

4 Likes

Disjointness based on associated types RFC (closed). I recommend reading the comments, and maybe even some of the linked issues, to anyone interested in the concerns and difficulties around negative reasoning, exclusive traits, and the like.

There's a work around explored in the last comment, and here's another. I believe they are the same or similar to each other and to your workaround above, but didn't take the time to actually compare and contrast them.

2 Likes

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.