I have a codebase with the same logic (to get a Vector of bytes) copy-pasted repeatedly:
pub fn main() {
let name = String::from("steve");
let age: u64 = 20;
let bytes: Vec<u8> = [name.as_bytes().to_vec(), age.to_le_bytes().to_vec()].concat();
print!("{:#?}", bytes);
}
To ensure the logic is consistent, I would like to create a function to generate the bytes based on the inputs, using the appropriate method from the input’s type:
enum SeedInput {
U64,
String,
}
impl SeedInput {
pub fn to_bytes(&self) -> &[u8] {
match self {
SeedInput::U64 => self.to_le_bytes(),
SeedInput::String => self.as_bytes(),
}
}
}
pub fn create_seeds_from_inputs(inputs: Vec<&SeedInput>) -> Vec<&[u8]> {
let mut seeds: Vec<&[u8]> = Vec::new();
for input in inputs {
seeds.push(input.to_bytes());
}
seeds
}
I understand the errors I am receiving: no method named 'to_le_bytes' found for reference `&SeedInput` in the current scope
, but my understanding is that inside this part of the match
statement we should have already narrowed the type of SeedInput
to be U64
.
Why isn’t Rust narrowing the type? How can I run a method that is specific to the matched type?
14
Your code did not work for three reasons.
-
SeedInput::U64
andSeedInput::String
do not represent actual Rust types, they are variants of an enum. -
Your
SeedInput
enum does not carry any data, as pointed out by justinas. When you callself.to_le_bytes()
inside your match expression, you are calling it onSeedInput
, which is zero-sized. What you should be doing is calling it on an instance ofu64
. -
There are ownership issues with your code also. You aim to return
&[u8]
, which would work well with theString
variant, but cannot work with theu64
variant. This is becauseu64::to_le_bytes()
returns a[u8; 8]
, which is then owned by the current function,SeedInput::to_bytes()
. It is therefore a temporary value, and you cannot return a reference to it.
The only way to make this work is by returning a Vec<u8>
from SeedInput::to_bytes()
.
Why?
Because
- you cannot return a reference, because that is not possible with
u64
. - you cannot return an owned
[u8; {const}]
, becauseString
is dynamically sized, you cannot know how big of an array you will need.
enum SeedInput {
U64(u64),
String(String)
}
impl SeedInput {
pub fn to_bytes(&self) -> Vec<u8> {
match self {
Self::U64(num) => num.to_le_bytes().to_vec(),
Self::String(s) => s.as_bytes().to_vec()
}
}
}
As people pointed out in the comments, the Rust Analyzer message re:
enum SeedInput {
u64,
string,
}
convert the identifier to upper camel case:
U64
rustc(non_camel_case_types)
Which asked me to convert them to TitleCase was misleading. I modified the code to use TitleCase:
enum SeedInput {
U64,
String,
}
Which is not what I was seeking to do. The correct syntax for an Enum here is actually:
enum Input {
U64Input(u64),
StringInput(String),
}
I did look in the Rust Book for this but couldn’t couldn’t find anything.
The following code then works:
trait Input {
fn to_bytes(&self) -> Vec<u8>;
}
impl Input for u64 {
fn to_bytes(&self) -> Vec<u8> {
self.to_le_bytes().to_vec()
}
}
impl Input for String {
fn to_bytes(&self) -> Vec<u8> {
self.as_bytes().to_vec()
}
}
fn inputs_to_bytes(inputs: Vec<Box<dyn Input>>) -> Vec<u8> {
let mut bytes: Vec<u8> = Vec::new();
for input in inputs {
bytes.extend(input.to_bytes());
}
bytes
}
macro_rules! vec_inputs {
( $( $x:expr ),* ) => {
{
let mut temp_vec: Vec<Box<dyn Input>> = Vec::new();
$(
temp_vec.push(Box::new($x));
)*
temp_vec
}
};
}
pub fn main() {
let name = String::from("steve");
let age: u64 = 20;
let bytes = inputs_to_bytes(vec_inputs![name, age]);
print!("{:#?}", bytes);
}