Skip to content

Conversation

jbangelo
Copy link
Collaborator

@jbangelo jbangelo commented Mar 2, 2022

While perusing the Rust API guidelines I noticed that it recommended implementing the Display trait for all types that it makes sense for, and several of the signal types already have function to convert to and from strings. I've exposed those functions in this PR and implemented Display for the associated types as well. I've also tried to update the from_str() functions to operate on more typical Rust data instead of low level CStr or CString types.

@jbangelo jbangelo requested review from silverjam and notoriaga March 2, 2022 05:54
Comment on lines 94 to 95
vec.push(0);
let c_str = ffi::CString::from_vec_with_nul(vec).unwrap();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To simplify things I'm just always pushing a null terminator to the end of the string. Does anyone have a better suggestion here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could do something like

    pub fn from_bytes<T: AsRef<[u8]>>(s: T) -> Result<Constellation, InvalidConstellation> {
        let bytes = s.as_ref();
        let bytes = if matches!(bytes.last(), Some(0)) {
            Cow::Borrowed(bytes)
        } else {
            let mut bytes = bytes.to_vec();
            bytes.push(0);
            Cow::Owned(bytes)
        };
        let c_str = ffi::CStr::from_bytes_with_nul(&bytes).unwrap();
        let constellation = unsafe { swiftnav_sys::constellation_string_to_enum(c_str.as_ptr()) };

        Self::from_constellation_t(constellation)
    }

Copy link
Contributor

@notoriaga notoriaga Mar 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And we should probably also probably be using little helper functions to reduce the cost of monomorphization

    /// Attempts to make a [`Constellation`] from bytes
    pub fn from_bytes<T: AsRef<[u8]>>(s: T) -> Result<Constellation, InvalidConstellation> {
        fn inner(bytes: &[u8]) -> Result<Constellation, InvalidConstellation> {
            let bytes = if matches!(bytes.last(), Some(0)) {
                Cow::Borrowed(bytes)
            } else {
                let mut bytes = bytes.to_vec();
                bytes.push(0);
                Cow::Owned(bytes)
            };
            let c_str = ffi::CStr::from_bytes_with_nul(&bytes).unwrap();
            let constellation =
                unsafe { swiftnav_sys::constellation_string_to_enum(c_str.as_ptr()) };
            Constellation::from_constellation_t(constellation)
        }
        inner(s.as_ref())
    }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might also make sense to implement FromStr if we have a method to parse from bytes

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'll just implement FromStr. The intention for these functions is to be an inverse to the to_str functions and not to necessarily operate on any arbitrary byte array. While the C interface has to blur the lines between strings and byte arrays I'd prefer to keep the Rust interface dealing in valid string types to make their use more clear.

I somehow missed CString::new() on my first pass which seems to deal with the null terminator problem itself, but also raises a different issue of a null character in the middle of the string. I think we can treat that like any other conversion error. For these functions both "BAD STRING" and "BAD\0STRING" should both result in the same error, which is simply a failed conversion.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intention for these functions is to be an inverse to the to_str functions and not to necessarily operate on any arbitrary byte array.

Ahh yeah that makes sense to me

I somehow missed CString::new() on my first pass which seems to deal with the null terminator problem itself, but also raises a different issue of a null character in the middle of the string.

CString::new() would require taking ownership of the bytes to perform the conversion. If we go with CStr we don't have to move/clone the bytes in the case the string already has a null byte. CStr doesn't have the equivalent new function but the check I have above should be pretty similar? I guess we shouldn't be unwraping the result from from_bytes_with_nul as that might occur because of a null byte in the middle of the string

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like the FFI documentation suggests to use CString for converting Rust strings into C compatible strings and CStr when converting C strings into something Rust can easily use. I think this is because Rust strings will only very rarely have null bytes at the very end, so you'll always need to copy the string contents over and append a null byte to make is C compatible and CString::new() does this automatically for you. The chances of being able to avoid an allocation by using CStr seem small.

Copy link
Contributor

@silverjam silverjam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing to add over @notoriaga's comments

@jbangelo jbangelo merged commit 92f9631 into master Apr 14, 2022
@jbangelo jbangelo deleted the jbangelo/additional-str branch April 14, 2022 03:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants