Can const generics do this yet?

It would be nice if this sort of thing worked. But apparently not yet.

fn read_numeric<T>(cursor: &mut dyn Read) -> Result<T, Error> {
    let mut b: [T; T::BITS/8] = [0; T::BITS/8];
    cursor.read_exact(&mut b)?; // read one value
    Ok(T::from_be_bytes(b))
}

Also, if we're going to have BITS for all the built-in types, BYTES, usable at compile time, would be nice.

You don't really need it, though:

mod stuff { /* ... */ }
use stuff::{T, BITS};

fn read_numeric<T>(cursor: &mut dyn Read) -> Result<T, Error> {
    let mut b: [T; BITS/8] = [0; BITS/8];
    cursor.read_exact(&mut b)?; // read one value
    Ok(T::from_be_bytes(b))
}

Or did you mean the actual size of the type, in bits? Why not use the size_of then?

trait BytesT: Sized {
    fn bytes() -> usize {
        std::mem::size_of::<Self>()
    }
}

trait BitsT: Sized {
    fn bits() -> usize {
        std::mem::size_of::<Self>() * 8
    }
}

enum Example {
    One(String),
    Two(i32, i32)
}

// implement only for types needed

impl BitsT for Example {}
impl BytesT for Example {}

// or make it genetic over T: Sized

impl<T: Sized> BitsT for T {}
impl<T: Sized> BytesT for T {}

fn main() {
    let e = Example::bits();
    println!("bits: {}", e);
    let e = Example::bytes();
    println!("bytes: {}", e);
}

playground link

1 Like

I think @John_Nagle wanted a const version of that. You don't need to change much for that :

trait BytesT: Sized {
    const BYTES: usize = std::mem::size_of::<Self>();
}

enum Example {
    One(String),
    Two(i32, i32)
}

impl<T: Sized> BytesT for T {}

fn main() {
    let e = Example::BYTES;
    println!("bytes: {}", e);
}

And add a constraint :

fn read_numeric<T>(cursor: &mut dyn Read) -> Result<T, Error>
where
    T: BytesT,
{
    let mut b: [T; T::BYTES] = [0; T::BYTES];
    cursor.read_exact(&mut b)?; // read one value
    Ok(T::from_be_bytes(b))
}

Though really, why not just add a Sized constraint and use std::mem::size_of directly.

Currently, from_be_bytes is an inherent method of numeric types so there's no way to call it in a generic context. You'd need to define a trait for that:

pub trait FromBigEndian: Sized {
    type Bytes: Sized + Default + AsMut<[u8]>;
    fn from_be(bytes: Self::Bytes) -> Self;
    fn read_be<R:std::io::Read>(mut cursor: R)->Result<Self, std::io::Error> {
        let mut b: Self::Bytes = Default::default();
        cursor.read_exact(b.as_mut())?;
        Ok(Self::from_be(b))
    }
}

macro_rules! impl_from_big_endian {
    ($($T:ty),*) => {
        $(impl FromBigEndian for $T {
            type Bytes = [u8; std::mem::size_of::<Self>()];
            fn from_be(bytes: Self::Bytes)->Self { Self::from_be_bytes(bytes) }
        })*
    }
}

impl_from_big_endian!{ u8, u16, u32 }

pub fn read_numeric<T>(cursor: &mut dyn std::io::Read) -> Result<T, std::io::Error>
where
    T: FromBigEndian,
{
    T::read_be(cursor)
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f4f06ddf9ce4d802371d9ffe58cb45e4

2 Likes

I didn't even read the body of that function beyond the array declaration, my bad

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.