Bizarre trait resolution issue with `typenum`

⚓ Rust    📅 2025-10-30    👤 surdeus    👁️ 7      

surdeus

Warning

This post was published 33 days ago. The information described in this article may have changed.

I am attempting to write a trait that represents types which are infallibly (and injectively) constructable from a buffer of bytes of a statically known size (types needn't be plain-old-data, so bytemuck::Pod/zerocopy::FromBytes are too restrictive, but Deserialize is too weak). Ideally this would be defined with const generics, but I can't use associated constants in array lengths, so I am using the hybrid_array and typenum crates for this. The current trait looks like:

// i don't think this can be a crate versioning issue
// since I'm using the version of  typenum re-exported by hybrid_array
use hybrid_array::{typenum, Array, ArraySize};
trait FromByteRepr {
    type Size: ArraySize;
    fn from_bytes(bytes: &Array<u8, Self::Size>) -> Self;
}

This mostly seems to work fine, however when speccing out a derive macro for this trait, I'm running into some strange issues on structs that contain both generic and concrete types (solely generic or solely concrete seems to work fine). As an example:

// dummy implementation
impl FromByteRepr for i32 {
    type Size = typenum::U4;
    fn from_bytes(bytes: &Array<u8, Self::Size>) -> Self {
        i32::from_le_bytes(bytes.0)
    }
}

struct Mixed<T> {
    generic: T,
    concrete: i32,
}

use std::ops::Add;
impl<T> FromByteRepr for Mixed<T>
where 
    T: FromByteRepr,
    T::Size: Add<<i32 as FromByteRepr>::Size>,
    // the exact details of the ArraySize trait aren't important
    // _any_ trait bound here causes the error 
    <T::Size as Add<<i32 as FromByteRepr>::Size>>::Output: ArraySize, 
{
    // just a placeholder to make the example minimal
    // in reality this would be typenum::Sum<T::Size, i32::Size>
    type Size = typenum::U1; 
    fn from_bytes(bytes: &Array<u8, Self::Size>) -> Self {
        todo!()
    }
}

This causes the following error:

error[E0277]: cannot add `UInt<UInt<UInt<UTerm, B1>, B0>, B0>` to `<T as foo::FromByteRepr>::Size`
  --> src/lib.rs:79:5
   |
79 | /     impl<T> FromByteRepr for Mixed<T>
80 | |     where
81 | |         T: FromByteRepr,
82 | |         T::Size: Add<<i32 as FromByteRepr>::Size>,
83 | |         <T::Size as Add<<i32 as FromByteRepr>::Size>>::Output: ArraySize,
   | |_________________________________________________________________________^ no implementation for `<T as foo::FromByteRepr>::Size + UInt<UInt<UInt<UTerm, B1>, B0>, B0>`
   |
   = help: the trait `Add<UInt<UInt<UInt<UTerm, B1>, B0>, B0>>` is not implemented for `<T as foo::FromByteRepr>::Size`
help: consider further restricting the associated type
   |
83 |         <T::Size as Add<<i32 as FromByteRepr>::Size>>::Output: ArraySize, <T as foo::FromByteRepr>::Size: Add<UInt<UInt<UInt<UTerm, B1>, B0>, B0>>
   |                                                                           ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

It seems to be telling me that I need to ensure that T::Size implements Add<typenum::U4> but thats what the constraint on line 82 says. Rust even knows that <i32 as FromByteRepr>::Size> == typenum::U4 because if I add the silly constraint i32: FromByteRepr<Size = typenum::U4> (and remove the offending constraint on line 83), compilation is successful.

Bizarrely, seemingly more complicated implementations that involve many generic parameters seem to work fine, but this one does not. Am I doing something obviously wrong, or is this a limitation of the compiler/typenum?

1 post - 1 participant

Read full topic

🏷️ Rust_feed