1

I'd like to create a struct that has a byte array, where a particular instance may have different sizes based on the creation of the struct known at compile time.

I've created a contrived example using a struct that has a byte representation of a floating point number with a separate type field. The working implementation below:

#![feature(float_to_from_bytes)]

#[derive(Debug)]
enum TypeMarker {
    NUMBER = 0x00,  // f64
    // BOOLEAN: u8 = 0x01, // bool
    // STRING: u8 = 0x02,  // UTF-8 string
}

#[derive(Debug)]
struct Value {
    t: TypeMarker,
    bytes: [u8; 8]
}

impl From<f64> for Value {
    fn from(v: f64) -> Self {
        Value {
            t: TypeMarker::NUMBER,
            bytes: v.to_be_bytes()
        }
    }
}

fn main() {
  let num = 4.0;
  println!("num = {:?}", num);

  let v1 = Value::from(4.0);
  println!("Value::from(4.0) = {:?}", v1);

  let v2:Value = num.into();
  println!("num.into() = {:?}", v2);

}

This working example (see also repo on github) uses rust nightly.

Running the example... cargo +nightly run --example into

produces the result I expect:

num = 4.0
Value::from(4.0) = Value { t: NUMBER, bytes: [64, 16, 0, 0, 0, 0, 0, 0] }
num.into() = Value { t: NUMBER, bytes: [64, 16, 0, 0, 0, 0, 0, 0] }

However, what I want to do is to support various types of numbers where the size is known at compile time. To illustrate this question, the example below adds impl From<i32> (which is 4 bytes long):

#![feature(float_to_from_bytes)]

#[derive(Debug)]
enum TypeMarker {
    NUMBER = 0x00,  // f64
    // BOOLEAN: u8 = 0x01, // bool
    // STRING: u8 = 0x02,  // UTF-8 string
}

#[derive(Debug)]
struct Value {
    t: TypeMarker,
    bytes: [u8; 8]
}

impl From<f64> for Value {
    fn from(v: f64) -> Self {
        Value {
            t: TypeMarker::NUMBER,
            bytes: v.to_be_bytes()
        }
    }
}

impl From<i32> for Value {
    fn from(v: i32) -> Self {
        Value {
            t: TypeMarker::NUMBER,
            bytes: v.to_be_bytes()
        }
    }
}


fn main() {
  let num = 4.0;
  println!("num = {:?}", num);

  let v1 = Value::from(4.0);
  println!("Value::from(4.0) = {:?}", v1);

  let v2:Value = num.into();
  println!("num.into() = {:?}", v2);

}

this produces the following error

error[E0308]: mismatched types
  --> examples/into.rs:33:20
   |
33 |             bytes: v.to_be_bytes()
   |                    ^^^^^^^^^^^^^^^ expected an array with a fixed size of 8 elements, found one with 4 elements
   |
   = note: expected type `[u8; 8]`
              found type `[u8; 4]`

I would like to declare Value struct so that it can be created with variable sized arrays of bytes (where the size is known at compile time).

I've tried:

struct Value {
    t: TypeMarker,
    bytes: [u8; usize]
}
error[E0423]: expected value, found builtin type `usize`
  --> examples/into.rs:17:17
   |
17 |     bytes: [u8; usize]
   |                 ^^^^^ not a value

error[E0277]: arrays only have std trait implementations for lengths 0..=32
  --> examples/into.rs:17:5
   |
17 |     bytes: [u8; usize]
   |     ^^^^^^^^^^^^^^^^^^ the trait `std::array::LengthAtMost32` is not implemented for `[u8; _]`
   |
   = note: required because of the requirements on the impl of `std::fmt::Debug` for `[u8; _]`
   = note: required because of the requirements on the impl of `std::fmt::Debug` for `&[u8; _]`
   = note: required for the cast to the object type `dyn std::fmt::Debug`

So then I tried:

struct Value {
    t: TypeMarker,
    bytes: [u8; _]
}

that didn't work either:

error: expected expression, found reserved identifier `_`
  --> examples/into.rs:17:17
   |
17 |     bytes: [u8; _]
   |                 ^ expected expression

error: aborting due to previous error

This seems like it should be possible and I think I've read the syntax for this once, but I've re-read many sections of the Rust book and looked at dozens of other posts and can't quite seem to figure out the syntax.

Question: How to change bytes declaration to fix the example above that illustrates the error? And, if that is not supported or not idiomatic, what approach would work?

1
  • Rust's built-in array has to have a constant length, a size known at compile time that varies will not do. For that, you'll have to use growable array type Vec. But not all hopes are lost; RFC 2000 constant generics is to address this exact limitation. Commented Oct 11, 2019 at 16:49

1 Answer 1

2

Array are allocated on the stack so it would be much easier to create a struct that has this property using the heap.

My suggestion is to either use a Vec for the bytes field

#[derive(Debug)]
struct Value {
    t: TypeMarker,
    bytes: Vec<u8>
}

or to use a boxed array:

#![feature(float_to_from_bytes)]

use std::boxed::Box;

#[derive(Debug)]
enum TypeMarker {
    NUMBER = 0x00,  // f64
    // BOOLEAN: u8 = 0x01, // bool
    // STRING: u8 = 0x02,  // UTF-8 string
}

#[derive(Debug)]
struct Value {
    t: TypeMarker,
    bytes: Box<[u8]>,
}

impl From<f64> for Value{
    fn from(v: f64) -> Self {
        Value {
            t: TypeMarker::NUMBER,
            bytes: Box::new(v.to_be_bytes()),
        }
    }
}

impl From<i32> for Value{
    fn from(v: i32) -> Self {
        Value {
            t: TypeMarker::NUMBER,
            bytes: Box::new(v.to_be_bytes()),
        }
    }
}


fn main() {
  let num = 4.0;
  println!("num = {:?}", num);

  let v1 = Value::from(4.0);
  println!("Value::from(4.0) = {:?}", v1);

  let v2:Value = num.into();
  println!("num.into() = {:?}", v2);

}

You can get some additional reading on using dynamically sized types here.

Hope this helps! Good luck!

Sign up to request clarification or add additional context in comments.

1 Comment

Appreciate the examples, since I'm still wrapping my brain around Rust syntax. For the first example, it seems I can set bytes field with v.to_be_bytes().to_vec() which compiles, so at least now I can play with some different implementations, even inefficient for many small numbers. Thx!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.