`generic_const_exprs` in trait bounds

The feature generic_const_exprs works as expected, but it seems it can limit functions, but not trait bounds.

#![feature(generic_const_exprs)]
#![allow(incomplete_features)]

#[derive(Copy, Clone, Debug)]
struct A<const N: usize>([u64; N]);

struct Condition<const U: bool>;

trait IsTrue {}
impl IsTrue for Condition<true> {}

impl<const N: usize, const M: usize> From<A<M>> for A<N> 
    where 
        Condition<{N > M}>: IsTrue
{
    fn from(v: A::<M>) -> Self {
        let mut out = [0; N];
        
        for i in 0..M {
            out[i] = v.0[i];
        }
        
        Self(out)
    }
}

fn main() {
    let a = A([1, 2, 3]);
    println!("{a:?}");
    
    let b: A::<4> = a.into();
    println!("{b:?}");
    
    let c: A::<5> = a.into();
    println!("{c:?}");
}

produces an error:

Compiling playground v0.0.1 (/playground)
error[E0119]: conflicting implementations of trait From<A<_>> for type A<_>
--> src/main.rs:12:1
|
12 | / impl<const N: usize, const M: usize> From<A> for A
13 | | where
14 | | Condition<{N > M}>: IsTrue
| |__________________________________^
|
= note: conflicting implementation in crate core:
- impl From for T;

For more information about this error, try rustc --explain E0119.
error: could not compile playground (bin "playground") due to 1 previous error

How can this problem be solved without explicit specialization for all possible N and M?

If not specialization, you need negative implementations and reasoning about them in coherence, which enables other things we don't have yet, like mutually exclusive traits.

I don't think your playground is possible via blanket implementation, even with incomplete features (but I could be wrong). Perhaps use a custom trait instead. Incidentally, if you do use incomplete_features, don't be surprised if you encounter compiler bugs and future breakage.[1]


  1. for example â†Šī¸Ž

1 Like

I've found some kind of recursion, meaning that A<N> implements From<A<{N - 1}>>. I think it should be a way to have the expected behavior with a similar system, but when I try something like From<A<{N + X}>> I get a recursion limit error (even if X is hardcoded, like 2 for example.

I think the problem is not totally unsolvable, but the solution may be long to compile and/or poorly optimized.

#![feature(generic_const_exprs)]
#![allow(incomplete_features)]

#[derive(Copy, Clone, Debug)]
struct A<const N: usize>([u64; N]);

struct Condition<const U: bool>;
trait IsTrue {}
impl IsTrue for Condition<true> {}

impl From<A<0>> for A<1>
{
    fn from(_v: A::<0>) -> Self {
        Self([0; 1])
    }
}

impl<const N: usize> From<A<N>> for A<{N + 1}>
where
    Condition<{N != 0}>: IsTrue,
    A<N>: From<A<{N - 1}>>
{
    fn from(v: A::<N>) -> Self {
        let mut out = [0; N+1];
        for i in 0..N {
            out[i] = v.0[i]    
        }
        Self(out)
    }
}

fn main() {
    let _a: A<2> = A([1]).into();
    let _b: A<5> = <A<3> as Into<A<{2+1+1}>>>::into(A([1;3])).into();
}
1 Like

The idea of a separate trait is good and seems to work overall:

#![feature(generic_const_exprs)]
#![allow(incomplete_features)]

#[derive(Copy, Clone, Debug)]
struct A<const N: usize>([u64; N]);

trait Cast<T> {
    fn cast(self) -> T;
}

struct Condition<const U: bool>;
trait IsTrue {}
impl IsTrue for Condition<true> {}

impl<const N: usize, const M: usize> Cast<A<N>> for A<M> 
    where 
        Condition<{N > M}>: IsTrue,
{
    fn cast(self) -> A<N> {
        let mut out = [0; N];
        
        for i in 0..M {
            out[i] = self.0[i];
        }
        
        A(out)
    }
}

fn main() {
    let a = A([1]);
    println!("{a:?}");
    
    let b: A::<2> = a.cast();
    println!("{b:?}");
    
    let c: A::<3> = a.cast();
    println!("{c:?}");
}

But! Only when everything is in a single compilation unit. If the implementation is inside lib, and the method call happens in another crate (for example, integration tests), then the call does not compile, resulting in an error:

the following trait bounds were not satisfied:

Moreover, it doesn't work specifically for generic_const_exprs: if you replace

impl<const N: usize, const M: usize> Cast<A<N>> for A<M> 
    where 
       Condition<{N > M}>: IsTrue,
{
   ...
}
   

with

impl<const N: usize, const M: usize> Cast<A<N>> for A<M> 
    where 
       Condition<true>: IsTrue,
{
   ...
}

all works, albeit without restrictions.
It seems that this trick is not very transparent between crate compilation.

Maybe related to this... but more generally, yeah, it's an incomplete feature. Don't expect a smooth ride.

1 Like

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.