Foreign trait restrictions on native types make generics hard to use

⚓ Rust    📅 2026-05-05    👤 surdeus    👁️ 2      

surdeus

Traits seem to have been created contemplating a use case where they are implemented as needed for specific types. There is one kind of case where this pattern is vexing, mathematical types.

For example, many crates end up implementing their own 3D and 2D vector types because there is no native vector algebra library in rust. This leads to developers having to convert between types across library uses even when the vast majority of the time the types are equivalent both in terms of byte layout and in terms of operations.

A way around this is to try to make blanket implementations, but these then start clashing with foreign trait constraints. I will give an example that I am struggling with.

I already have a trait that abstracts away Vector types, it works, I am using it. But for long reasons to explain I now need it to work with scalar types, but for it to work I need to be able to index numbers. Consider this snippet that's trying to do just that:

trait Indexable {
    type Item;
    fn at(&self, i: usize) -> &Self::Item;
    fn at_mut(&mut self, _i: usize) -> &mut Self::Item;
}

impl Indexable for f32 {
    type Item = f32;

    fn at(&self, _i: usize) -> &Self::Item {
        self
    }

    fn at_mut(&mut self, _i: usize) -> &mut Self::Item {
        &mut *self
    }
}

impl<T, I> Indexable for T
where T: Index<usize, Output = I> + IndexMut<usize, Output = I>
{
    type Item = I;

    fn at(&self, i: usize) -> &Self::Item {
        &self[i]
    }

    fn at_mut(&mut self, i: usize) -> &mut Self::Item {
        &mut self[i]
    }
}

This won't compile because f32 and the index traits are foreign and so it could technically be that one day the index trait is implemented for f32. However, it is not currently implemented for it and this is precisely trying to unify behaviour between indexable objects like vectors and slices and scalars, which is what I need.

The only alternative would be to make a wrapping type, but if I do a wrapping type, then it automatically requires the user to do explicit casts, but the entire point of what I am doing is to avoid having to do casts between types that are algebraically equivalent.

I feel stuck here.

1 post - 1 participant

Read full topic

🏷️ Rust_feed