genericsrusttraitsconst-genericsassociated-const

How to make a trait that uses an array whose length changes with each implementer?


I have a trait that specifies a deserialization function for its implementor. This trait should take in a fixed size array of u8 where the length of this array depends on the implementer (e.g. type A might be deserializable from 10 bytes, type B might be deserializable from 50 bytes). Unfortunately I just can't get this trait to work when I want it to support the deserialization of arrays of a type implementing my trait, I always run into errors related to generics not being usable in const expressions. One important thing to note is that my code is running in a quite particular environment where I need to know the size of my calldata at compile time, so I can't switch this out for Vec and be done with it unfortunately. Here's what I've tried so far:

Attempt 1:

trait MyTrait {
    const ARR_LEN: usize;

    fn deserialize<const N: usize>(array: [u8; N]) -> Self;
}

impl<const M: usize, T> MyTrait for [T; M]
where
    T: MyTrait,
{
    const ARR_LEN: usize = M * T::ARR_LEN;

    fn deserialize<const N: usize>(array: [u8; N]) -> Self {
        array
            .chunks_exact(T::ARR_LEN)
            .map(|chunk| T::deserialize::<{T::ARR_LEN}>(chunk.try_into().unwrap()))
            .collect::<Vec<_>>()
            .try_into()
            .unwrap()
    }

}

This gives an error in the T::deserialize::<{T::ARR_LEN}> part of the deserialization function, namely that we can't use a type generic in a const expression.

Attempt 2:

trait MyTrait {
    const ARR_LEN: usize;

    fn deserialize(array: [u8; Self::ARR_LEN]) -> Self;
}

impl<const M: usize, T> MyTrait for [T; M]
where
    T: MyTrait,
{
    const ARR_LEN: usize = M * T::ARR_LEN;

    fn deserialize(array: [u8; ARR_LEN]) -> Self {
        array
            .chunks_exact(T::ARR_LEN)
            .map(|chunk| T::deserialize(chunk.try_into().unwrap()))
            .collect::<Vec<_>>()
            .try_into()
            .unwrap()
    }

}

This gives an error in the fn deserialize(array: [u8; ARR_LEN]) -> Self { part, also that I can't use an generic in a const expression (since ARR_LEN is constructed using T::ARR_LEN).

Attempt 3:

trait MyTrait {
    const ARR_LEN: usize;
    type ArrayType: Sized;

    fn deserialize(array: Self::ArrayType) -> Self;
}

impl<const M: usize, T> MyTrait for [T; M]
where
    T: MyTrait,
{
    const ARR_LEN: usize = M * T::ARR_LEN;
    type ArrayType = [u8; Self::ARR_LEN];

    fn deserialize(array: Self::ArrayType) -> Self {
        array
            .chunks_exact(T::ARR_LEN)
            .map(|chunk| T::deserialize(chunk.try_into().unwrap()))
            .collect::<Vec<_>>()
            .try_into()
            .unwrap()
    }

}

This gives an error in the type ArrayType = [u8; Self::ARR_LEN] part, as above that I can't use an generic in a const expression (since ARR_LEN is constructed using T::ARR_LEN here too).

Does anyone have an idea or different approach of how I could get this to work (without having to resort to using nightly with #![feature(generic_const_exprs)])?


Solution

  • You can use generic const parameter instead of associate const,

    trait MyTrait<const N: usize> {
        fn deser(array: [u8; N]) -> Self;
    }
    
    #[derive(Debug)]
    struct MyStruct {
        data: u8,
    }
    
    impl MyTrait<1> for MyStruct {
        fn deser(array: [u8; 1]) -> Self {
            Self { data: array[0] }
        }
    }
    
    impl<const N: usize> MyTrait<N> for [MyStruct; N] {
        fn deser(array: [u8; N]) -> Self {
            array.map(|e| MyStruct::deser([e]))
        }
    }
    
    fn main() {
        let array = [0x01];
        let my_struct = MyStruct::deser(array);
        println!("{:?}", my_struct);
    
        let array = [0x01, 0x02, 0x03];
        let my_array = <[MyStruct; 3]>::deser(array);
        println!("{:?}", my_array);
    }
    
    MyStruct { data: 1 }
    [MyStruct { data: 1 }, MyStruct { data: 2 }, MyStruct { data: 3 }]
    

    what happen if the const don't match

    let array = [0x01, 0x02, 0x03];
    let my_array = <[MyStruct; 4]>::deser(array);
    
    error[E0308]: mismatched types
    --> ***\src/main.rs:33:43
    |
    33 |     let my_array = <[MyStruct; 4]>::deser(array);
    |                    ---------------------- ^^^^^ expected an array with a fixed size of 4 elements, found one with 3 elements
    |                    |
    |                    arguments to this function are incorrect
    |
    note: associated function defined here
    --> ***\src/main.rs:2:8
    |
    2  |     fn deser(array: [u8; N]) -> Self;
    |        ^^^^^
    
    For more information about this error, try `rustc --explain E0308`.