How to call `to_be_bytes` on a generic type?

I'm writing some toys for practicing, and get trouble to call some method on type a generic type T, here's the code:

pub fn my_to_be_bytes<T>(value: T, index: u32) -> u8 {
    let slice = value.to_be_bytes();
    return slice[index];
}

when I try to instantiate this template:

let x = 0x79u32;
my_to_be_bytes(x, 0);

The compiler spawn an error:

no method named to_be_bytes found for type parameter T in the current scope

I got some questions here:

  1. The compiler didn't tell me what the type T is, does this error happens before a "template instantiation"?

  2. If so, does that mean rust only allow call a method on T only if I specified like this:

pub fn my_to_be_bytes<T : SomeTraitWhichHasTheMethod_to_be_bytes>(value: T, index: u32) -> u8 {
    let slice = value.to_be_bytes();
    return slice[index];
}
  1. If so, I found that to_be_bytes is not implemented in traits, but implemented directly for "primitive types", how could I write the function to call to_be_bytes()?

  2. Without loss of generality, what's the general way to call a "non-trait" method on a type T?

  1. Yes
  2. Yes
  3. You could make a trait with a to_be_bytes method and implement it for the types you care about
  4. You can't do that

This is a misunderstanding; templates will check validity after substituting for a concrete type, whereas generics will do so before substituting.

Which leads me into:

Yup, the idea is that you can constrain types based solely on traits and lifetimes, and can't randomly call methods or access fields (even though they might exist).

Try a crate such as num_traits. The to_[ne/be/le]_bytes methods are only implemented for certain numeric types.

This can't be done.

1 Like

Hi, I checked the crate num_traits, it seems that I can use the trait bounds to create method conditionally, such as method for singed and another method for unsigned, but I didn't find any method like to_[ne/be/le]_bytes. Do you suggest that to create my own trait which implements the to_[ne/be/le]_bytes?

Another question is that is to_[ne/be/le]_bytes implemented on types instead of on traits designed on purpose? Does it have any good to implement them on traits?

Somewhat contrary to the other posts here, I want to note that you can sort of do this with a macro:

// e.g. get_byte!(17_u32, 2)
macro_rules! get_byte {
    ($x:expr, $i:expr) => {
        $x.to_be_bytes()[$i]
    };
}

This will be checked after substitution like a C++ template.

1 Like

Rust traits work only when a trait is explicitly imported or required in a generic context. So if these methods were only in a trait, you'd need something like use std::num::TheTraitWithToBeBytes in every file that uses it.

There's been a proposal to for inherent traits that behave like both methods and traits, but it's nowhere near being a reality.

I tried to wrap to_be_bytes into traits like this:

trait ToBeBytes {
    type Element;
    fn to_be_bytes(&self) -> [u8; mem::size_of::<Self::Element>()];
}

but the compiler said:

associated type Element not found for Self

any ideas why this error happens?

In this instance, I assume you'd first tried to write:

trait ToBytes {
    fn to_bytes(self) -> [u8; std::mem::size_of::<Self>()];
}

And later switched to this:

trait ToBeBytes {
    type Element;
    fn to_be_bytes(&self) -> [u8; mem::size_of::<Self::Element>()];
}

Where an implementer could substitute Element with Self.

But the compiler warned you in the first instance of the following:

error: generic parameters may not be used in const operations
 --> src/lib.rs:2:51
  |
2 |     fn to_bytes(self) -> [u8; std::mem::size_of::<Self>()];
  |                                                   ^^^^ cannot perform const operation using `Self`
  |
  = note: type parameters may not be used in const expressions

error: aborting due to previous error

Which is a current limitation of Rust's const generics. In other words, there is no way to say an equivalent to this in stable rust today.

To do this, I'd probably just stick to native endian and use something like bytemuck, or return a Vec or an ArrayVec or SmallVec.

  • bytemuck will allow you to get a reference to a slice from a reference to a number. It performs the following conversion: &T -> &[u8] where T: Pod (Plain old data, which isn't implemented for structures with, for example, pointers, such as String).

  • SmallVec stores a few elements inline before it spills onto the heap.

  • ArrayVec stores all of its elements in an array (inline) with a hard cap:

    let mut av = ArrayVec::<u8, 2>::new();
    av.push(12u8);
    av.push(34u8);
    // Will panic!
    av.push(56u8);
    

    Which more closely resembles an array.

This will work too:

trait ToBeBytes {
    type ByteArray: AsRef<[u8]>; // = [u8; 8], etc.
    fn to_be_bytes(&self) -> Self::ByteArray;
}
2 Likes

@psionic12, check out eio.

Hi, how can I determine the size of the ByteArray here? It looks to me that the code have no idea, I need to be careful about the boundary of the array.

It'll be specified when implementing the trait:

impl ToBeBytes for u32 {
    type ByteArray = [u8; 4]; // here
    fn to_be_bytes(&self) -> Self::ByteArray {
        u32::to_be_bytes(*self)
    }
}

Playground

1 Like

I finally done by this:

trait ToBeBytes {
    type ReturnType;
    fn to_be_bytes(&self) -> Self::ReturnType;
}

impl ToBeBytes for u32 {
    type ReturnType = [u8; mem::size_of::<Self>()];
    fn to_be_bytes(&self) -> Self::ReturnType {
        return u32::to_be_bytes(*self);
    }
}

It seems that there's no need to give a type to ReturnType in the trait, does using type ByteArray: AsRef<[u8]>; has any advantage here?

That's not "giving type", that's a trait bound. Here, it serves more as a documentation remainder - in every implementation, ReturnType must be some kind of byte array.

1 Like

Thanks for pointing out that it's a trait bound, I have been thinking it as a type given so far. Add type restriction is reasonable indeed.

If you have a function that takes a generic ToBeBytes, this lets you use as_ref() on the return type without adding an extra bound on the function:

fn debug_be_bytes<T:ToBeBytes>(obj: &T) {
    let bytes = obj.to_be_bytes();
    dbg!(bytes.as_ref());
}
2 Likes

Without a trait bound it's impossible to use it generically, because ReturnType could be a u8, or () or File or whatever else, so it wouldn't have a length or access to bytes in a generic context.

If the current setup works for you, it means your code is hardcoded to know it can only be the u32 type, so it's not using the trait generically.

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.