U8 array to string

Hello guys

let's suppose that I have this struct:

struct A {
    f0: u8,
    f1; u16,
    description : [u8;16],
}

the instance of this struct is filled with data coming from a serial port.

What I want to do now is creating a json object like the following one and print it:

let object = serde_json::json!({
            "f0": a.f0,
            "f1": a.f1,
            "description": std::str::from_utf8(&a.description).unwrap(),
        });
println!("{}",serde_json::to_string_pretty(&object).unwrap());

The point is that "description" will be displayed with all its 16 elements. Instead I want to print just the first ones before the first '\0' char.

How can I do that ?

Thanks

F

You could use .split_once('\0').

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.

2 Likes

Hello kpreid ! Thanks !

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.

Thanks

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
                )
            }
        }

1 Like

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.

Then, you can use CStr::to_str() or CStr::to_string_lossy() get a regular string from that CStr.

3 Likes

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

I'm pretty sure split will never be empty (that is, return None for it's first next()), so you can just use .next().expect("split is never empty")

str::split is a bit better documented in the edge cases, but that makes sense.

Thanks, you're right, I just tested it.

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),
    }
}

3 Likes

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.