But you should also think about what you want to happen if the serial port gives you an actual '\0' character — does it make sense to truncate the string in that case? Or would you rather keep all the actually received characters? If so, you need to keep track of the actual length read rather than storing only the array.
Also, if this is a physical serial link at any point then you need to consider the possibility of data corruption — unwrap()ing on non-UTF-8 data may be inadequate error handling.
the "description" field is a null terminated string. For example, if the description is "Pippo" i would have description[5:15] = 0 or at least description[5]=0.
What I want is to "extract" just the relevant part (the one before the first '\0')
I'll take a lool to "splite_once" but it seems pretty complicated to me. I'm pretty new in this world.
Here is some code that may help to get started. It uses slice::split to break up the bytes into segments with no zeros, and of course you only need the first segment. Then it uses str::from_utf8 to convert the bytes to a str.
There are more concise ways to do this, but I tried to keep it straightforward since you're just getting started.
struct A {
f0: u8,
f1: u16,
description: [u8; 16],
}
impl A {
fn description_as_str(&self) -> Result<&str, Utf8Error> {
// This will create an iterator over non-zero slices
let mut iter = self.description.split(|&byte| byte == 0);
// iter.next() returns the first non-zero slice. If Some we
// convert that from a slice of bytes to a utf8 (str). This
// returns Ok(s) or a Utf8Error if the bytes are invalid UTF-8.
// If None then all bytes are zero and we return an emty str.
// We use Ok("") because the function returns a Result.
match iter.next() {
Some(slice) => std::str::from_utf8(slice),
None => Ok(""),
}
}
}
// Data from serial port
let a = A {
f0: 0,
f1: 1,
description: [
0x41, 0x42, 0x43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
],
};
match a.description_as_str() {
Ok(s) => println!("Use this string 's' in JSON object: {}", s),
Err(err) => {
println!(
"Handle the error: {err} for invalid bytes: {:?}",
a.description
)
}
}
Ah, then in this case, you want CStr::from_bytes_until_nul(). That will give you an &CStr (or an error if there is no nul), which is the appropriate type for dealing with null-terminated strings.
Noting that this requires the nul, IIRC, so if it's actually "up to 16 bytes, terminated with a null if it fits" which I've seen, they'll need the split approach
If there is no null byte it returns FromBytesUntilNulError rather than panicking, and then the entire slice can be converted to utf8. Which still looks better than using split:
fn description_as_str(&self) -> Result<&str, Utf8Error> {
use std::ffi::CStr;
use std::str;
match CStr::from_bytes_until_nul(&self.description) {
Ok(cstr) => cstr.to_str(),
Err(_) => str::from_utf8(&self.description),
}
}