From 5eb7cad8dd8adda6a7954f7a1f8a5e6910db1af0 Mon Sep 17 00:00:00 2001 From: Nikolai Morin Date: Fri, 18 Mar 2022 12:11:58 +0100 Subject: [PATCH 01/40] Message generation refactoring (#80) Previously, only messages consisting of basic types and strings were supported. Now, all message types will work, including those that have fields of nested types, bounded types, or arrays. Changes: - The "rsext" library is deleted - Unused messages in "rosidl_generator_rs" are deleted - There is a new package, "rosidl_runtime_rs", see below - The RMW-compatible messages from C, which do not require an extra conversion step, are exposed in addition to the "idiomatic" messages - Publisher and subscription are changed to work with both idiomatic and rmw types, through the unifying `Message` trait On `rosidl_runtime_rs`: This package is the successor of `rclrs_msg_utilities` package, but doesn't have much in common with it anymore. It provides common types and functionality for messages. The `String` and `Sequence` types and their variants in that package essentially wrap C types from the `rosidl_runtime_c` package and C messages generated by the "rosidl_generator_c" package. A number of functions and traits are implemented on these types, so that they feel as ergonomic as possible, for instance, a `seq!` macro for creating a sequence. There is also some documentation and doctests. The memory for the (non-pretty) message types is managed by the C allocator. Not yet implemented: - long double - constants - Services/clients - @verbatim comments - ndarray for sequences/arrays of numeric types - implementing `Eq`, `Ord` and `Hash` when a message contains no floats --- rosidl_runtime_rs/Cargo.toml | 15 + rosidl_runtime_rs/build.rs | 23 + rosidl_runtime_rs/package.xml | 19 + rosidl_runtime_rs/src/lib.rs | 11 + rosidl_runtime_rs/src/sequence.rs | 705 ++++++++++++++++++++++++++++++ rosidl_runtime_rs/src/string.rs | 454 +++++++++++++++++++ rosidl_runtime_rs/src/traits.rs | 141 ++++++ 7 files changed, 1368 insertions(+) create mode 100644 rosidl_runtime_rs/Cargo.toml create mode 100644 rosidl_runtime_rs/build.rs create mode 100644 rosidl_runtime_rs/package.xml create mode 100644 rosidl_runtime_rs/src/lib.rs create mode 100644 rosidl_runtime_rs/src/sequence.rs create mode 100644 rosidl_runtime_rs/src/string.rs create mode 100644 rosidl_runtime_rs/src/traits.rs diff --git a/rosidl_runtime_rs/Cargo.toml b/rosidl_runtime_rs/Cargo.toml new file mode 100644 index 0000000..572c874 --- /dev/null +++ b/rosidl_runtime_rs/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "rosidl_runtime_rs" +version = "0.1.0" +authors = ["Jacob Hassold ", "Nikolai Morin "] +edition = "2021" + +[lib] +path = "src/lib.rs" + +[dependencies] +downcast-rs = "1.2.0" +libc = "0.2" + +[dev-dependencies] +quickcheck = "1" \ No newline at end of file diff --git a/rosidl_runtime_rs/build.rs b/rosidl_runtime_rs/build.rs new file mode 100644 index 0000000..fbc3c85 --- /dev/null +++ b/rosidl_runtime_rs/build.rs @@ -0,0 +1,23 @@ +use std::env; +use std::path::Path; + +const AMENT_PREFIX_PATH: &str = "AMENT_PREFIX_PATH"; + +fn get_env_var_or_abort(env_var: &'static str) -> String { + if let Ok(value) = env::var(env_var) { + value + } else { + panic!( + "{} environment variable not set - please source ROS 2 installation first.", + env_var + ); + } +} + +fn main() { + let ament_prefix_path_list = get_env_var_or_abort(AMENT_PREFIX_PATH); + for ament_prefix_path in ament_prefix_path_list.split(':') { + let library_path = Path::new(ament_prefix_path).join("lib"); + println!("cargo:rustc-link-search=native={}", library_path.display()); + } +} diff --git a/rosidl_runtime_rs/package.xml b/rosidl_runtime_rs/package.xml new file mode 100644 index 0000000..a0a5f01 --- /dev/null +++ b/rosidl_runtime_rs/package.xml @@ -0,0 +1,19 @@ + + + + rosidl_runtime_rs + 0.0.1 + Message generation code shared by Rust projects in ROS 2 + Jacob Hassold + Nikolai Morin + Apache License 2.0 + Jacob Hassold + Nikolai Morin + + rosidl_runtime_c + + ament_cargo + + diff --git a/rosidl_runtime_rs/src/lib.rs b/rosidl_runtime_rs/src/lib.rs new file mode 100644 index 0000000..567f385 --- /dev/null +++ b/rosidl_runtime_rs/src/lib.rs @@ -0,0 +1,11 @@ +//! Bindings to `rosidl_runtime_c` and related functionality for messages. + +#[macro_use] +mod sequence; +pub use sequence::{BoundedSequence, Sequence, SequenceExceedsBoundsError}; + +mod string; +pub use string::{BoundedString, BoundedWString, String, StringExceedsBoundsError, WString}; + +mod traits; +pub use traits::{Message, RmwMessage, SequenceAlloc}; diff --git a/rosidl_runtime_rs/src/sequence.rs b/rosidl_runtime_rs/src/sequence.rs new file mode 100644 index 0000000..aeb1f27 --- /dev/null +++ b/rosidl_runtime_rs/src/sequence.rs @@ -0,0 +1,705 @@ +use std::cmp::Ordering; +use std::fmt::{self, Debug, Display}; +use std::hash::{Hash, Hasher}; +use std::iter::{Extend, FromIterator, FusedIterator}; +use std::ops::{Deref, DerefMut}; + +use crate::traits::SequenceAlloc; + +/// An unbounded sequence. +/// +/// The layout of a concrete `Sequence` is the same as the corresponding `Sequence` struct +/// generated by `rosidl_generator_c`. For instance, +/// `rosidl_runtime_rs::Sequence` is the same +/// as `std_msgs__msg__String__Sequence`. See the [`Message`](crate::Message) trait for background +/// information on this topic. +/// +/// +/// # Example +/// +/// ``` +/// # use rosidl_runtime_rs::{Sequence, seq}; +/// let mut list = Sequence::::new(3); +/// assert_eq!(&list[..], &[0, 0, 0]); +/// // Sequences deref to slices +/// list[0] = 3; +/// list[1] = 2; +/// list[2] = 1; +/// assert_eq!(&list[..], &[3, 2, 1]); +/// // Alternatively, use the seq! macro +/// list = seq![3, 2, 1]; +/// // The default sequence is empty +/// assert!(Sequence::::default().is_empty()); +/// ``` +#[repr(C)] +pub struct Sequence { + data: *mut T, + size: libc::size_t, + capacity: libc::size_t, +} + +/// A bounded sequence. +/// +/// The layout of a concrete `BoundedSequence` is the same as the corresponding `Sequence` +/// struct generated by `rosidl_generator_c`. For instance, +/// `rosidl_runtime_rs::BoundedSequence` +/// is the same as `std_msgs__msg__String__Sequence`, which also represents both bounded +/// sequences. See the [`Message`](crate::Message) trait for background information on this +/// topic. +/// +/// # Example +/// +/// ``` +/// # use rosidl_runtime_rs::{BoundedSequence, seq}; +/// let mut list = BoundedSequence::::new(3); +/// assert_eq!(&list[..], &[0, 0, 0]); +/// // BoundedSequences deref to slices +/// list[0] = 3; +/// list[1] = 2; +/// list[2] = 1; +/// assert_eq!(&list[..], &[3, 2, 1]); +/// // Alternatively, use the seq! macro with the length specifier +/// list = seq![5 # 3, 2, 1]; +/// // The default bounded sequence is empty +/// assert!(BoundedSequence::::default().is_empty()); +/// ``` +#[derive(Clone)] +#[repr(transparent)] +pub struct BoundedSequence { + inner: Sequence, +} + +/// Error type for [`BoundedSequence::try_new()`]. +#[derive(Debug)] +pub struct SequenceExceedsBoundsError { + len: usize, + upper_bound: usize, +} + +/// A by-value iterator created by [`Sequence::into_iter()`] and [`BoundedSequence::into_iter()`]. +pub struct SequenceIterator { + seq: Sequence, + idx: usize, +} + +// ========================= impl for Sequence ========================= + +impl Clone for Sequence { + fn clone(&self) -> Self { + let mut seq = Self::default(); + if T::sequence_copy(self, &mut seq) { + seq + } else { + panic!("Cloning Sequence failed") + } + } +} + +impl Debug for Sequence { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + self.as_slice().fmt(f) + } +} + +impl Default for Sequence { + fn default() -> Self { + Self { + data: std::ptr::null_mut(), + size: 0, + capacity: 0, + } + } +} + +impl Deref for Sequence { + type Target = [T]; + fn deref(&self) -> &Self::Target { + self.as_slice() + } +} + +impl DerefMut for Sequence { + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_mut_slice() + } +} + +impl Drop for Sequence { + fn drop(&mut self) { + T::sequence_fini(self) + } +} + +impl Eq for Sequence {} + +impl Extend for Sequence { + fn extend(&mut self, iter: I) + where + I: IntoIterator, + { + let it = iter.into_iter(); + // The index in the sequence where the next element will be stored + let mut cur_idx = self.size; + // Convenience closure for resizing self + let resize = |seq: &mut Self, new_size: usize| { + let old_seq = std::mem::replace(seq, Sequence::new(new_size)); + for (i, elem) in old_seq.into_iter().enumerate().take(new_size) { + seq[i] = elem; + } + }; + // First, when there is a size hint > 0 (lower bound), make room for + // that many elements. + let num_remaining = it.size_hint().0; + if num_remaining > 0 { + let new_size = self.size.saturating_add(num_remaining); + resize(self, new_size); + } + for item in it { + // If there is no more capacity for the next element, resize to the + // next power of two. + // + // A pedantic implementation would check for usize overflow here, but + // that is hardly possible on real hardware. Also, not the entire + // usize address space is usable for user space programs. + if cur_idx == self.size { + let new_size = (self.size + 1).next_power_of_two(); + resize(self, new_size); + } + self[cur_idx] = item; + cur_idx += 1; + } + // All items from the iterator are stored. Shrink the sequence to fit. + if cur_idx < self.size { + resize(self, cur_idx); + } + } +} + +impl From<&[T]> for Sequence { + fn from(slice: &[T]) -> Self { + let mut seq = Sequence::new(slice.len()); + seq.clone_from_slice(slice); + seq + } +} + +impl From> for Sequence { + fn from(v: Vec) -> Self { + Sequence::from_iter(v) + } +} + +impl FromIterator for Sequence { + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + let mut seq = Sequence::new(0); + seq.extend(iter); + seq + } +} + +impl Hash for Sequence { + fn hash(&self, state: &mut H) { + self.as_slice().hash(state) + } +} + +impl IntoIterator for Sequence { + type Item = T; + type IntoIter = SequenceIterator; + fn into_iter(self) -> Self::IntoIter { + SequenceIterator { seq: self, idx: 0 } + } +} + +impl Ord for Sequence { + fn cmp(&self, other: &Self) -> Ordering { + self.as_slice().cmp(other.as_slice()) + } +} + +impl PartialEq for Sequence { + fn eq(&self, other: &Self) -> bool { + self.as_slice().eq(other.as_slice()) + } +} + +impl PartialOrd for Sequence { + fn partial_cmp(&self, other: &Self) -> Option { + self.as_slice().partial_cmp(other.as_slice()) + } +} + +impl Sequence +where + T: SequenceAlloc, +{ + /// Creates a sequence of `len` elements with default values. + pub fn new(len: usize) -> Self { + let mut seq = Self::default(); + if !T::sequence_init(&mut seq, len) { + panic!("Sequence initialization failed"); + } + seq + } + + /// Extracts a slice containing the entire sequence. + /// + /// Equivalent to `&seq[..]`. + pub fn as_slice(&self) -> &[T] { + // SAFETY: self.data points to self.size consecutive, initialized elements and + // isn't modified externally. + unsafe { std::slice::from_raw_parts(self.data, self.size) } + } + + /// Extracts a mutable slice containing the entire sequence. + /// + /// Equivalent to `&mut seq[..]`. + pub fn as_mut_slice(&mut self) -> &mut [T] { + // SAFETY: self.data points to self.size consecutive, initialized elements and + // isn't modified externally. + unsafe { std::slice::from_raw_parts_mut(self.data, self.size) } + } +} + +impl Sequence { + /// Internal function for the sequence_copy impl. To be removed when rosidl#650 is backported and released. + pub fn resize_to_at_least(&mut self, len: usize) { + let allocation_size = std::mem::size_of::() * len; + if self.capacity < len { + // SAFETY: The memory in self.data is owned by C. + let data = unsafe { libc::realloc(self.data as *mut _, allocation_size) } as *mut T; + if data.is_null() { + panic!("realloc failed"); + } + // Initialize the new memory + for i in self.capacity..len { + // SAFETY: i is in bounds, and write() is appropriate for initializing uninitialized memory + unsafe { + data.add(i).write(T::default()); + } + } + self.data = data; + self.size = len; + self.capacity = len; + } + } +} + +// ========================= impl for BoundedSequence ========================= + +impl Debug for BoundedSequence { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + self.as_slice().fmt(f) + } +} + +impl Default for BoundedSequence { + fn default() -> Self { + Self { + inner: Sequence { + data: std::ptr::null_mut(), + size: 0, + capacity: 0, + }, + } + } +} + +impl Deref for BoundedSequence { + type Target = [T]; + fn deref(&self) -> &Self::Target { + self.inner.deref() + } +} + +impl DerefMut for BoundedSequence { + fn deref_mut(&mut self) -> &mut Self::Target { + self.inner.deref_mut() + } +} + +impl Drop for BoundedSequence { + fn drop(&mut self) { + T::sequence_fini(&mut self.inner) + } +} + +impl Eq for BoundedSequence {} + +impl Extend for BoundedSequence { + fn extend(&mut self, iter: I) + where + I: IntoIterator, + { + self.inner + .extend(iter.into_iter().take(N - self.inner.size)); + } +} + +impl TryFrom<&[T]> for BoundedSequence { + type Error = SequenceExceedsBoundsError; + fn try_from(slice: &[T]) -> Result { + let mut seq = BoundedSequence::try_new(slice.len())?; + seq.clone_from_slice(slice); + Ok(seq) + } +} + +impl TryFrom> for BoundedSequence { + type Error = SequenceExceedsBoundsError; + fn try_from(v: Vec) -> Result { + if v.len() > N { + Err(SequenceExceedsBoundsError { + len: v.len(), + upper_bound: N, + }) + } else { + Ok(BoundedSequence::from_iter(v)) + } + } +} + +impl FromIterator for BoundedSequence { + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + let mut seq = BoundedSequence::new(0); + seq.extend(iter); + seq + } +} + +impl Hash for BoundedSequence { + fn hash(&self, state: &mut H) { + self.as_slice().hash(state) + } +} + +impl IntoIterator for BoundedSequence { + type Item = T; + type IntoIter = SequenceIterator; + fn into_iter(mut self) -> Self::IntoIter { + let seq = std::mem::replace( + &mut self.inner, + Sequence { + data: std::ptr::null_mut(), + size: 0, + capacity: 0, + }, + ); + SequenceIterator { seq, idx: 0 } + } +} + +impl Ord for BoundedSequence { + fn cmp(&self, other: &Self) -> Ordering { + self.as_slice().cmp(other.as_slice()) + } +} + +impl PartialEq for BoundedSequence { + fn eq(&self, other: &Self) -> bool { + self.as_slice().eq(other.as_slice()) + } +} + +impl PartialOrd for BoundedSequence { + fn partial_cmp(&self, other: &Self) -> Option { + self.as_slice().partial_cmp(other.as_slice()) + } +} + +impl BoundedSequence +where + T: SequenceAlloc, +{ + /// Creates a sequence of `len` elements with default values. + /// + /// If `len` is greater than `N`, this function panics. + pub fn new(len: usize) -> Self { + Self::try_new(len).unwrap() + } + + /// Attempts to create a sequence of `len` elements with default values. + /// + /// If `len` is greater than `N`, this function returns an error. + pub fn try_new(len: usize) -> Result { + if len > N { + return Err(SequenceExceedsBoundsError { + len, + upper_bound: N, + }); + } + let mut seq = Self::default(); + if !T::sequence_init(&mut seq.inner, len) { + panic!("BoundedSequence initialization failed"); + } + Ok(seq) + } + + /// Extracts a slice containing the entire sequence. + /// + /// Equivalent to `&seq[..]`. + pub fn as_slice(&self) -> &[T] { + self.inner.as_slice() + } + + /// Extracts a mutable slice containing the entire sequence. + /// + /// Equivalent to `&mut seq[..]`. + pub fn as_mut_slice(&mut self) -> &mut [T] { + self.inner.as_mut_slice() + } +} + +// ========================= impl for SequenceIterator ========================= + +impl Iterator for SequenceIterator { + type Item = T; + fn next(&mut self) -> Option { + if self.idx >= self.seq.size { + return None; + } + // SAFETY: data + idx is in bounds and points to a valid value + let elem = unsafe { + let ptr = self.seq.data.add(self.idx); + let elem = ptr.read(); + // Need to make sure that dropping the sequence later will not fini() the elements + ptr.write(std::mem::zeroed::()); + elem + }; + self.idx += 1; + Some(elem) + } + + fn size_hint(&self) -> (usize, Option) { + let len = (self.seq.size + 1) - self.idx; + (len, Some(len)) + } +} + +impl ExactSizeIterator for SequenceIterator { + fn len(&self) -> usize { + (self.seq.size + 1) - self.idx + } +} + +impl FusedIterator for SequenceIterator {} + +// ========================= impl for StringExceedsBoundsError ========================= + +impl Display for SequenceExceedsBoundsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!( + f, + "BoundedSequence with upper bound {} initialized with len {}", + self.upper_bound, self.len + ) + } +} + +impl std::error::Error for SequenceExceedsBoundsError {} + +macro_rules! impl_sequence_alloc_for_primitive_type { + ($rust_type:ty, $init_func:ident, $fini_func:ident, $copy_func:ident) => { + #[link(name = "rosidl_runtime_c")] + extern "C" { + fn $init_func(seq: *mut Sequence<$rust_type>, size: libc::size_t) -> bool; + fn $fini_func(seq: *mut Sequence<$rust_type>); + } + + impl SequenceAlloc for $rust_type { + fn sequence_init(seq: &mut Sequence, size: libc::size_t) -> bool { + // SAFETY: There are no special preconditions to the sequence_init function. + unsafe { + // This allocates space and sets seq.size and seq.capacity to size + let ret = $init_func(seq as *mut _, size); + // Zero memory, since it will be uninitialized if there is no default value + std::ptr::write_bytes(seq.data, 0u8, size); + ret + } + } + fn sequence_fini(seq: &mut Sequence) { + // SAFETY: There are no special preconditions to the sequence_fini function. + unsafe { $fini_func(seq as *mut _) } + } + fn sequence_copy(in_seq: &Sequence, out_seq: &mut Sequence) -> bool { + let allocation_size = std::mem::size_of::() * in_seq.size; + if out_seq.capacity < in_seq.size { + // SAFETY: The memory in out_seq.data is owned by C. + let data = unsafe { libc::realloc(out_seq.data as *mut _, allocation_size) }; + if data.is_null() { + return false; + } + out_seq.data = data as *mut _; + out_seq.capacity = in_seq.size; + } + // SAFETY: The memory areas don't overlap. + unsafe { + libc::memcpy( + out_seq.data as *mut _, + in_seq.data as *const _, + allocation_size, + ); + } + out_seq.size = in_seq.size; + true + } + } + }; +} + +// Primitives are not messages themselves, but there can be sequences of them. +// +// See https://github.com/ros2/rosidl/blob/master/rosidl_runtime_c/include/rosidl_runtime_c/primitives_sequence.h +// Long double isn't available in Rust, so it is skipped. +impl_sequence_alloc_for_primitive_type!( + f32, + rosidl_runtime_c__float__Sequence__init, + rosidl_runtime_c__float__Sequence__fini, + rosidl_runtime_c__float__Sequence__copy +); +impl_sequence_alloc_for_primitive_type!( + f64, + rosidl_runtime_c__double__Sequence__init, + rosidl_runtime_c__double__Sequence__fini, + rosidl_runtime_c__double__Sequence__copy +); +impl_sequence_alloc_for_primitive_type!( + bool, + rosidl_runtime_c__boolean__Sequence__init, + rosidl_runtime_c__boolean__Sequence__fini, + rosidl_runtime_c__boolean__Sequence__copy +); +impl_sequence_alloc_for_primitive_type!( + u8, + rosidl_runtime_c__uint8__Sequence__init, + rosidl_runtime_c__uint8__Sequence__fini, + rosidl_runtime_c__uint8__Sequence__copy +); +impl_sequence_alloc_for_primitive_type!( + i8, + rosidl_runtime_c__int8__Sequence__init, + rosidl_runtime_c__int8__Sequence__fini, + rosidl_runtime_c__int8__Sequence__copy +); +impl_sequence_alloc_for_primitive_type!( + u16, + rosidl_runtime_c__uint16__Sequence__init, + rosidl_runtime_c__uint16__Sequence__fini, + rosidl_runtime_c__uint16__Sequence__copy +); +impl_sequence_alloc_for_primitive_type!( + i16, + rosidl_runtime_c__int16__Sequence__init, + rosidl_runtime_c__int16__Sequence__fini, + rosidl_runtime_c__int16__Sequence__copy +); +impl_sequence_alloc_for_primitive_type!( + u32, + rosidl_runtime_c__uint32__Sequence__init, + rosidl_runtime_c__uint32__Sequence__fini, + rosidl_runtime_c__uint32__Sequence__copy +); +impl_sequence_alloc_for_primitive_type!( + i32, + rosidl_runtime_c__int32__Sequence__init, + rosidl_runtime_c__int32__Sequence__fini, + rosidl_runtime_c__int32__Sequence__copy +); +impl_sequence_alloc_for_primitive_type!( + u64, + rosidl_runtime_c__uint64__Sequence__init, + rosidl_runtime_c__uint64__Sequence__fini, + rosidl_runtime_c__uint64__Sequence__copy +); +impl_sequence_alloc_for_primitive_type!( + i64, + rosidl_runtime_c__int64__Sequence__init, + rosidl_runtime_c__int64__Sequence__fini, + rosidl_runtime_c__int64__Sequence__copy +); + +/// Creates a sequence, similar to the `vec!` macro. +/// +/// It's possible to create both unbounded and bounded sequences. +/// Unbounded sequences are created by a comma-separated list of values. +/// Bounded sequences are created by additionally specifying the maximum capacity (the `N` type +/// parameter) in the beginning, followed by a `#`. +/// +/// # Example +/// ``` +/// # use rosidl_runtime_rs::{BoundedSequence, Sequence, seq}; +/// let unbounded: Sequence = seq![1, 2, 3]; +/// let bounded: BoundedSequence = seq![5 # 1, 2, 3]; +/// assert_eq!(&unbounded[..], &bounded[..]) +/// ``` +#[macro_export] +macro_rules! seq { + [$( $elem:expr ),*] => { + { + let len = seq!(@count_tts $($elem),*); + let mut seq = Sequence::new(len); + let mut i = 0; + $( + seq[i] = $elem; + #[allow(unused_assignments)] + { i += 1; } + )* + seq + } + }; + [$len:literal # $( $elem:expr ),*] => { + { + let len = seq!(@count_tts $($elem),*); + let mut seq = BoundedSequence::<_, $len>::new(len); + let mut i = 0; + $( + seq[i] = $elem; + #[allow(unused_assignments)] + { i += 1; } + )* + seq + } + }; + // https://danielkeep.github.io/tlborm/book/blk-counting.html + (@replace_expr ($_t:expr, $sub:expr)) => {$sub}; + (@count_tts $($e:expr),*) => {<[()]>::len(&[$(seq!(@replace_expr ($e, ()))),*])}; +} + +#[cfg(test)] +mod tests { + use super::*; + use quickcheck::quickcheck; + + quickcheck! { + fn test_extend(xs: Vec, ys: Vec) -> bool { + let mut xs_seq = Sequence::new(xs.len()); + xs_seq.copy_from_slice(&xs); + xs_seq.extend(ys.clone()); + if xs_seq.len() != xs.len() + ys.len() { + return false; + } + if &xs_seq[..xs.len()] != &xs[..] { + return false; + } + if &xs_seq[xs.len()..] != &ys[..] { + return false; + } + true + } + } + + quickcheck! { + fn test_iteration(xs: Vec) -> bool { + let mut seq_1 = Sequence::new(xs.len()); + seq_1.copy_from_slice(&xs); + let seq_2 = seq_1.clone().into_iter().collect(); + seq_1 == seq_2 + } + } +} diff --git a/rosidl_runtime_rs/src/string.rs b/rosidl_runtime_rs/src/string.rs new file mode 100644 index 0000000..9584032 --- /dev/null +++ b/rosidl_runtime_rs/src/string.rs @@ -0,0 +1,454 @@ +use std::cmp::Ordering; +use std::convert::TryFrom; +use std::ffi::CStr; +use std::fmt::{self, Debug, Display}; +use std::hash::{Hash, Hasher}; +use std::ops::{Deref, DerefMut}; + +use crate::sequence::Sequence; +use crate::traits::SequenceAlloc; + +/// A zero-terminated string of 8-bit characters. +/// +/// The layout of this type is the same as `rosidl_runtime_c__String`. See the +/// [`Message`](crate::Message) trait for background information on this topic. +/// +/// +/// # Example +/// +/// ``` +/// # use rosidl_runtime_rs::String; +/// let mut s = String::from("Grüß Gott!"); +/// // Conversion back to a std::string::String is done with the ToString trait from the standard +/// // library. +/// assert_eq!(&s.to_string(), "Grüß Gott!"); +/// ``` +#[repr(C)] +pub struct String { + /// Dynamic memory in this type is allocated and deallocated by C, but this is a detail that is managed by + /// the relevant functions and trait impls. + data: *mut libc::c_char, + size: libc::size_t, + capacity: libc::size_t, +} + +/// A zero-terminated string of 16-bit characters. +/// +/// The layout of this type is the same as `rosidl_runtime_c__U16String`. See the +/// [`Message`](crate::Message) trait for background information on this topic. +/// +/// # Example +/// +/// ``` +/// # use rosidl_runtime_rs::WString; +/// let mut s = WString::from("Grüß Gott!"); +/// // Conversion back to a std::string::String is done with the ToString trait from the standard +/// // library. +/// assert_eq!(&s.to_string(), "Grüß Gott!"); +/// ``` +#[repr(C)] +pub struct WString { + data: *mut libc::c_ushort, + size: libc::size_t, + capacity: libc::size_t, +} + +/// A zero-terminated string of 8-bit characters with a length limit. +/// +/// The same as [`String`], but it cannot be constructed from a string that is too large. +/// The length is measured as the number of Unicode scalar values, not bytes. +/// +/// # Example +/// +/// ``` +/// # use rosidl_runtime_rs::BoundedString; +/// # use std::convert::TryFrom; +/// let mut maybe_str = BoundedString::<3>::try_from("noo!"); +/// assert!(maybe_str.is_err()); +/// maybe_str = BoundedString::<3>::try_from("ok!"); +/// assert!(maybe_str.is_ok()); +/// let bounded_str = maybe_str.unwrap(); +/// assert_eq!(&bounded_str.to_string(), "ok!"); +/// ``` +#[derive(Clone, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] +pub struct BoundedString { + inner: String, +} + +/// A zero-terminated string of 16-bit characters with a length limit. +/// +/// The same as [`WString`], but it cannot be constructed from a string that is too large. +/// The length is measured as the number of Unicode scalar values, not bytes. +/// +/// # Example +/// +/// ``` +/// # use rosidl_runtime_rs::BoundedWString; +/// # use std::convert::TryFrom; +/// let mut maybe_wstr = BoundedWString::<3>::try_from("noo!"); +/// assert!(maybe_wstr.is_err()); +/// maybe_wstr = BoundedWString::<3>::try_from("ok!"); +/// assert!(maybe_wstr.is_ok()); +/// let bounded_wstr = maybe_wstr.unwrap(); +/// assert_eq!(&bounded_wstr.to_string(), "ok!"); +/// ``` +#[derive(Clone, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] +pub struct BoundedWString { + inner: WString, +} + +/// Error type for [`BoundedString::try_from()`] and [`BoundedWString::try_from()`]. +#[derive(Debug)] +pub struct StringExceedsBoundsError { + len: usize, + upper_bound: usize, +} + +// ========================= impls for String and WString ========================= + +// There is a lot of redundancy between String and WString, which this macro aims to reduce. +macro_rules! string_impl { + ($string:ty, $char_type:ty, $string_conversion_func:ident, $init:ident, $fini:ident, $assignn:ident, $sequence_init:ident, $sequence_fini:ident, $sequence_copy:ident) => { + #[link(name = "rosidl_runtime_c")] + extern "C" { + fn $init(s: *mut $string) -> bool; + fn $fini(s: *mut $string); + fn $assignn(s: *mut $string, value: *const $char_type, n: libc::size_t) -> bool; + fn $sequence_init(seq: *mut Sequence<$string>, size: libc::size_t) -> bool; + fn $sequence_fini(seq: *mut Sequence<$string>); + } + + impl Default for $string { + fn default() -> Self { + let mut msg = Self { + data: std::ptr::null_mut(), + size: 0, + capacity: 0, + }; + // SAFETY: Passing in a zeroed string is safe. + if !unsafe { $init(&mut msg as *mut _) } { + panic!("Sinit failed"); + } + msg + } + } + + impl Clone for $string { + fn clone(&self) -> Self { + let mut msg = Self::default(); + // SAFETY: This is doing the same thing as rosidl_runtime_c__String__copy. + if !unsafe { $assignn(&mut msg as *mut _, self.data as *const _, self.size) } { + panic!("$assignn failed"); + } + msg + } + } + + impl Debug for $string { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + Debug::fmt(&self.to_string(), f) + } + } + + // It's not guaranteed that there are no interior null bytes, hence no Deref to CStr. + // This does not include the null byte at the end! + impl Deref for $string { + type Target = [$char_type]; + fn deref(&self) -> &Self::Target { + // SAFETY: self.data points to self.size consecutive, initialized elements and + // isn't modified externally. + unsafe { std::slice::from_raw_parts(self.data as *const $char_type, self.size) } + } + } + + impl DerefMut for $string { + fn deref_mut(&mut self) -> &mut Self::Target { + // SAFETY: self.data points to self.size consecutive, initialized elements and + // isn't modified externally. + unsafe { std::slice::from_raw_parts_mut(self.data as *mut $char_type, self.size) } + } + } + + impl Display for $string { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + let converted = std::string::String::$string_conversion_func(self.deref()); + Display::fmt(&converted, f) + } + } + + impl Drop for $string { + fn drop(&mut self) { + // SAFETY: There are no special preconditions to the fini function. + unsafe { + $fini(self as *mut _); + } + } + } + + impl Eq for $string {} + + impl Hash for $string { + fn hash(&self, state: &mut H) { + self.deref().hash(state) + } + } + + impl PartialEq for $string { + fn eq(&self, other: &Self) -> bool { + self.deref().eq(other.deref()) + } + } + + impl Ord for $string { + fn cmp(&self, other: &Self) -> Ordering { + self.deref().cmp(other.deref()) + } + } + + impl PartialOrd for $string { + fn partial_cmp(&self, other: &Self) -> Option { + self.deref().partial_cmp(other.deref()) + } + } + + impl SequenceAlloc for $string { + fn sequence_init(seq: &mut Sequence, size: libc::size_t) -> bool { + // SAFETY: There are no special preconditions to the sequence_init function. + unsafe { $sequence_init(seq as *mut _, size) } + } + fn sequence_fini(seq: &mut Sequence) { + // SAFETY: There are no special preconditions to the sequence_fini function. + unsafe { $sequence_fini(seq as *mut _) } + } + fn sequence_copy(in_seq: &Sequence, out_seq: &mut Sequence) -> bool { + out_seq.resize_to_at_least(in_seq.len()); + out_seq.clone_from_slice(in_seq.as_slice()); + true + } + } + }; +} + +string_impl!( + String, + u8, + from_utf8_lossy, + rosidl_runtime_c__String__init, + rosidl_runtime_c__String__fini, + rosidl_runtime_c__String__assignn, + rosidl_runtime_c__String__Sequence__init, + rosidl_runtime_c__String__Sequence__fini, + rosidl_runtime_c__String__Sequence__copy +); +string_impl!( + WString, + libc::c_ushort, + from_utf16_lossy, + rosidl_runtime_c__U16String__init, + rosidl_runtime_c__U16String__fini, + rosidl_runtime_c__U16String__assignn, + rosidl_runtime_c__U16String__Sequence__init, + rosidl_runtime_c__U16String__Sequence__fini, + rosidl_runtime_c__U16String__Sequence__copy +); + +impl From<&str> for String { + fn from(s: &str) -> Self { + let mut msg = Self { + data: std::ptr::null_mut(), + size: 0, + capacity: 0, + }; + // SAFETY: It's okay to pass a non-zero-terminated string here since assignn uses the + // specified length and will append the 0 byte to the dest string itself. + if !unsafe { + rosidl_runtime_c__String__assignn(&mut msg as *mut _, s.as_ptr() as *const _, s.len()) + } { + panic!("rosidl_runtime_c__String__assignn failed"); + } + msg + } +} + +impl String { + /// Creates a CStr from this String. + /// + /// This scales with the length of the string but does not create copy of the string. + /// See also [`CStr::from_ptr()`]. + pub fn to_cstr(&self) -> &CStr { + // SAFETY: self.data is a valid pointer and won't change. + // Also, the lifetime of the CStr is the same as self, which is correct. + unsafe { CStr::from_ptr(self.data as *const _) } + } +} + +impl From<&str> for WString { + fn from(s: &str) -> Self { + let mut msg = Self { + data: std::ptr::null_mut(), + size: 0, + capacity: 0, + }; + let buf: Vec = s.encode_utf16().collect(); + // SAFETY: It's okay to pass a non-zero-terminated string here since assignn uses the + // specified length and will append the 0 to the dest string itself. + if !unsafe { + rosidl_runtime_c__U16String__assignn( + &mut msg as *mut _, + buf.as_ptr() as *const _, + buf.len(), + ) + } { + panic!("rosidl_runtime_c__U16String__assignn failed"); + } + msg + } +} + +// ========================= impl for BoundedString ========================= + +impl Debug for BoundedString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + Debug::fmt(&self.inner, f) + } +} + +impl Deref for BoundedString { + type Target = [u8]; + fn deref(&self) -> &Self::Target { + self.inner.deref() + } +} + +impl DerefMut for BoundedString { + fn deref_mut(&mut self) -> &mut Self::Target { + self.inner.deref_mut() + } +} + +impl Display for BoundedString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + Display::fmt(&self.inner, f) + } +} + +impl SequenceAlloc for BoundedString { + fn sequence_init(seq: &mut Sequence, size: libc::size_t) -> bool { + // SAFETY: There are no special preconditions to the rosidl_runtime_c__String__Sequence__init function. + unsafe { + rosidl_runtime_c__String__Sequence__init(seq as *mut Sequence as *mut _, size) + } + } + fn sequence_fini(seq: &mut Sequence) { + // SAFETY: There are no special preconditions to the rosidl_runtime_c__String__Sequence__fini function. + unsafe { rosidl_runtime_c__String__Sequence__fini(seq as *mut Sequence as *mut _) } + } + fn sequence_copy(in_seq: &Sequence, out_seq: &mut Sequence) -> bool { + // SAFETY: Transmute of a transparent type to the inner type is fine + unsafe { + ::sequence_copy( + std::mem::transmute::<&Sequence, &Sequence>(in_seq), + std::mem::transmute::<&mut Sequence, &mut Sequence>(out_seq), + ) + } + } +} + +impl TryFrom<&str> for BoundedString { + type Error = StringExceedsBoundsError; + fn try_from(s: &str) -> Result { + let length = s.chars().count(); + if length <= N { + Ok(Self { + inner: String::from(s), + }) + } else { + Err(StringExceedsBoundsError { + len: length, + upper_bound: N, + }) + } + } +} + +// ========================= impl for BoundedWString ========================= + +impl Debug for BoundedWString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + Debug::fmt(&self.inner, f) + } +} + +impl Deref for BoundedWString { + type Target = [u16]; + fn deref(&self) -> &Self::Target { + self.inner.deref() + } +} + +impl DerefMut for BoundedWString { + fn deref_mut(&mut self) -> &mut Self::Target { + self.inner.deref_mut() + } +} + +impl Display for BoundedWString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + Display::fmt(&self.inner, f) + } +} + +impl SequenceAlloc for BoundedWString { + fn sequence_init(seq: &mut Sequence, size: libc::size_t) -> bool { + // SAFETY: There are no special preconditions to the rosidl_runtime_c__U16String__Sequence__init function. + unsafe { + rosidl_runtime_c__U16String__Sequence__init(seq as *mut Sequence as *mut _, size) + } + } + fn sequence_fini(seq: &mut Sequence) { + // SAFETY: There are no special preconditions to the rosidl_runtime_c__U16String__Sequence__fini function. + unsafe { rosidl_runtime_c__U16String__Sequence__fini(seq as *mut Sequence as *mut _) } + } + fn sequence_copy(in_seq: &Sequence, out_seq: &mut Sequence) -> bool { + // SAFETY: Transmute of a transparent type to the inner type is fine + unsafe { + ::sequence_copy( + std::mem::transmute::<&Sequence, &Sequence>(in_seq), + std::mem::transmute::<&mut Sequence, &mut Sequence>(out_seq), + ) + } + } +} + +impl TryFrom<&str> for BoundedWString { + type Error = StringExceedsBoundsError; + fn try_from(s: &str) -> Result { + let length = s.chars().count(); + if length <= N { + Ok(Self { + inner: WString::from(s), + }) + } else { + Err(StringExceedsBoundsError { + len: length, + upper_bound: N, + }) + } + } +} + +// ========================= impl for StringExceedsBoundsError ========================= + +impl Display for StringExceedsBoundsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!( + f, + "BoundedString with upper bound {} initialized with len {}", + self.upper_bound, self.len + ) + } +} + +impl std::error::Error for StringExceedsBoundsError {} diff --git a/rosidl_runtime_rs/src/traits.rs b/rosidl_runtime_rs/src/traits.rs new file mode 100644 index 0000000..7fb9640 --- /dev/null +++ b/rosidl_runtime_rs/src/traits.rs @@ -0,0 +1,141 @@ +// Copyright 2020 DCS Corporation, All Rights Reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// DISTRIBUTION A. Approved for public release; distribution unlimited. +// OPSEC #4584. +// +use std::borrow::Cow; +use std::fmt::Debug; + +/// Internal trait that connects a particular `Sequence` instance to generated C functions +/// that allocate and deallocate memory. +/// +/// User code never needs to call these trait methods, much less implement this trait. +pub trait SequenceAlloc: Sized { + fn sequence_init(seq: &mut crate::Sequence, size: libc::size_t) -> bool; + fn sequence_fini(seq: &mut crate::Sequence); + fn sequence_copy(in_seq: &crate::Sequence, out_seq: &mut crate::Sequence) -> bool; +} + +/// Trait for RMW-compatible messages. +/// +/// See the documentation for the [`Message`] trait, which is the trait that should generally be +/// used by user code. +/// +/// User code never needs to call this trait's method, much less implement this trait. +pub trait RmwMessage: Clone + Debug + Default { + fn get_type_support() -> libc::uintptr_t; +} + +/// Trait for types that can be used in a `rclrs::Subscription` and a `rclrs::Publisher`. +/// +/// `rosidl_generator_rs` generates two types of messages that implement this trait: +/// - An "idiomatic" message type, in the `${package_name}::msg` module +/// - An "RMW-compatible" message type, in the `${package_name}::msg::rmw` module +/// +/// # Idiomatic message type +/// The idiomatic message type aims to be familiar to Rust developers and ROS 2 developers coming +/// from `rclcpp`. +/// To this end, it translates the original ROS 2 message into a version that uses idiomatic Rust +/// structs: [`std::vec::Vec`] for sequences and [`std::string::String`] for strings. All other +/// fields are the same as in an RMW-compatible message. +/// +/// This conversion incurs some overhead when reading and publishing messages. +/// +/// It's possible to use the idiomatic type for a publisher and the RMW-compatible type for a +/// corresponding subscription, and vice versa. +/// +/// # RMW-compatible message type +/// The RMW-compatible message type aims to achieve higher performance by avoiding the conversion +/// step to an idiomatic message. +/// +/// It uses the following type mapping: +/// +/// | Message field type | Rust type | +/// |------------|---------------| +/// | `string` | [`String`](crate::String) | +/// | `wstring` | [`WString`](crate::WString) | +/// | `string<=N`, for example `string<=10` | [`BoundedString`](crate::BoundedString) | +/// | `wstring<=N`, for example `wstring<=10` | [`BoundedWString`](crate::BoundedWString) | +/// | `T[]`, for example `int32[]` | [`Sequence`](crate::Sequence) | +/// | `T[<=N]`, for example `int32[<=32]` | [`BoundedSequence`](crate::BoundedSequence) | +/// | `T[N]`, for example `float32[8]` | standard Rust arrays | +/// | primitive type, for example `float64` | corresponding Rust primitive type | +/// +///
+/// +/// The linked Rust types provided by this package are equivalents of types defined in C that are +/// used by the RMW layer. +/// +/// The API for these types, and the message as a whole, is still memory-safe and as convenient as +/// possible. +/// For instance, the [`Sequence`](crate::Sequence) struct that is used for sequences supports +/// iteration and all of the functionality of slices. However, it doesn't have an equivalent of +/// [`Vec::push()`], among others. +/// +/// ## What does "RMW-compatible" mean in detail? +/// The message can be directly passed to and from the RMW layer because (1) its layout is +/// identical to the layout of the type generated by `rosidl_generator_c` and (2) the dynamic +/// memory inside the message is owned by the C allocator. +/// +/// The above type mapping, together with a `#[repr(C)]` annotation on the message, guarantees +/// these two properties. +/// +/// This means the user of a message does not need to care about memory ownership, because that is +/// managed by the relevant functions and trait impls. +/// +/// ## I need even more detail, please +/// `rosidl_runtime_c` and the code generated by `rosidl_generator_c` manages +/// memory by means of four functions for each message: `init()`, `fini()`, `create()`, and +/// `destroy()`. +/// +/// `init()` does the following: +/// - for a message, it calls `init()` on all its members that are of non-primitive type, and applies default values +/// - for a primitive sequence, it allocates the space requested +/// - for a string, it constructs a string containing a single null terminator byte +/// - for a non-primitive sequence, it zero-allocates the space requested and calls `init()` on all its elements +/// +/// `fini()` does the following (which means after a call to `fini()`, everything inside the message has been deallocated): +/// - for a message, it calls `fini()` on all its members that are of non-primitive type +/// - for a primitive sequence, it deallocates +/// - for a string, it deallocates +/// - for a non-primitive sequence, it calls `fini()` on all its elements, and then deallocates +/// +/// `create()` simply allocates space for the message itself, and calls `init()`. +/// +/// `destroy()` simply deallocates the message itself, and calls `fini()`. +/// +/// Memory ownership by C is achieved by calling `init()` when any string or sequence is created, +/// as well as in the `Default` impl for messages. +/// User code can still create messages explicitly, which will not call `init()`, but this is not a +/// problem, since nothing is allocated this way. +/// The `Drop` impl for any sequence or string will call `fini()`. + +pub trait Message: Clone + Debug + Default + 'static { + /// The corresponding RMW-compatible message type. + type RmwMsg: RmwMessage; + + /// Converts the idiomatic message into an RMW-compatible message. + /// + /// If the idiomatic message is owned, a slightly more efficient conversion is possible. + /// This is why the function takes a `Cow`. + /// + /// If this function receives a borrowed message that is already RMW-compatible, it should + /// directly return that borrowed message. + /// This is why the return type is also `Cow`. + fn into_rmw_message<'a>(msg_cow: Cow<'a, Self>) -> Cow<'a, Self::RmwMsg>; + + /// Converts the RMW-compatible message into an idiomatic message. + fn from_rmw_message(msg: Self::RmwMsg) -> Self; +} From c4163a47f4e248e52972e6b37ef9c47468fd3f15 Mon Sep 17 00:00:00 2001 From: Nikolai Morin Date: Mon, 21 Mar 2022 14:56:50 +0100 Subject: [PATCH 02/40] Enable Clippy in CI (#83) --- rosidl_runtime_rs/src/traits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rosidl_runtime_rs/src/traits.rs b/rosidl_runtime_rs/src/traits.rs index 7fb9640..ae7c1df 100644 --- a/rosidl_runtime_rs/src/traits.rs +++ b/rosidl_runtime_rs/src/traits.rs @@ -134,7 +134,7 @@ pub trait Message: Clone + Debug + Default + 'static { /// If this function receives a borrowed message that is already RMW-compatible, it should /// directly return that borrowed message. /// This is why the return type is also `Cow`. - fn into_rmw_message<'a>(msg_cow: Cow<'a, Self>) -> Cow<'a, Self::RmwMsg>; + fn into_rmw_message(msg_cow: Cow<'_, Self>) -> Cow<'_, Self::RmwMsg>; /// Converts the RMW-compatible message into an idiomatic message. fn from_rmw_message(msg: Self::RmwMsg) -> Self; From 3818376d4d71ad2d69e45b4bb7a9d65d10d3db44 Mon Sep 17 00:00:00 2001 From: Nikolai Morin Date: Tue, 12 Apr 2022 21:01:58 +0200 Subject: [PATCH 03/40] Give subscription callback the owned message (#92) Also remove unused dependency from rosidl_runtime_rs --- rosidl_runtime_rs/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/rosidl_runtime_rs/Cargo.toml b/rosidl_runtime_rs/Cargo.toml index 572c874..3edac78 100644 --- a/rosidl_runtime_rs/Cargo.toml +++ b/rosidl_runtime_rs/Cargo.toml @@ -8,7 +8,6 @@ edition = "2021" path = "src/lib.rs" [dependencies] -downcast-rs = "1.2.0" libc = "0.2" [dev-dependencies] From 3c5a33f5a571516ee2b35ccfb2a89effac1b11d8 Mon Sep 17 00:00:00 2001 From: Nikolai Morin Date: Sun, 17 Apr 2022 14:47:36 +0200 Subject: [PATCH 04/40] Bump every package to version 0.2 (#100) --- rosidl_runtime_rs/Cargo.toml | 2 +- rosidl_runtime_rs/package.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rosidl_runtime_rs/Cargo.toml b/rosidl_runtime_rs/Cargo.toml index 3edac78..93b09a6 100644 --- a/rosidl_runtime_rs/Cargo.toml +++ b/rosidl_runtime_rs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rosidl_runtime_rs" -version = "0.1.0" +version = "0.2.0" authors = ["Jacob Hassold ", "Nikolai Morin "] edition = "2021" diff --git a/rosidl_runtime_rs/package.xml b/rosidl_runtime_rs/package.xml index a0a5f01..b9aa65c 100644 --- a/rosidl_runtime_rs/package.xml +++ b/rosidl_runtime_rs/package.xml @@ -4,7 +4,7 @@ schematypens="http://www.w3.org/2001/XMLSchema"?> rosidl_runtime_rs - 0.0.1 + 0.2.0 Message generation code shared by Rust projects in ROS 2 Jacob Hassold Nikolai Morin From d12be8c0368cb1cdae551b3b3b373b6c17d0002e Mon Sep 17 00:00:00 2001 From: Nikolai Morin Date: Thu, 21 Apr 2022 15:16:06 +0200 Subject: [PATCH 05/40] Document all public items (#94) --- rosidl_runtime_rs/src/lib.rs | 1 + rosidl_runtime_rs/src/sequence.rs | 6 +++--- rosidl_runtime_rs/src/traits.rs | 4 ++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/rosidl_runtime_rs/src/lib.rs b/rosidl_runtime_rs/src/lib.rs index 567f385..9cc8076 100644 --- a/rosidl_runtime_rs/src/lib.rs +++ b/rosidl_runtime_rs/src/lib.rs @@ -1,3 +1,4 @@ +#![warn(missing_docs)] //! Bindings to `rosidl_runtime_c` and related functionality for messages. #[macro_use] diff --git a/rosidl_runtime_rs/src/sequence.rs b/rosidl_runtime_rs/src/sequence.rs index aeb1f27..bf31fce 100644 --- a/rosidl_runtime_rs/src/sequence.rs +++ b/rosidl_runtime_rs/src/sequence.rs @@ -20,8 +20,8 @@ use crate::traits::SequenceAlloc; /// ``` /// # use rosidl_runtime_rs::{Sequence, seq}; /// let mut list = Sequence::::new(3); -/// assert_eq!(&list[..], &[0, 0, 0]); /// // Sequences deref to slices +/// assert_eq!(&list[..], &[0, 0, 0]); /// list[0] = 3; /// list[1] = 2; /// list[2] = 1; @@ -52,8 +52,8 @@ pub struct Sequence { /// ``` /// # use rosidl_runtime_rs::{BoundedSequence, seq}; /// let mut list = BoundedSequence::::new(3); -/// assert_eq!(&list[..], &[0, 0, 0]); /// // BoundedSequences deref to slices +/// assert_eq!(&list[..], &[0, 0, 0]); /// list[0] = 3; /// list[1] = 2; /// list[2] = 1; @@ -626,7 +626,7 @@ impl_sequence_alloc_for_primitive_type!( /// Creates a sequence, similar to the `vec!` macro. /// -/// It's possible to create both unbounded and bounded sequences. +/// It's possible to create both [`Sequence`]s and [`BoundedSequence`]s. /// Unbounded sequences are created by a comma-separated list of values. /// Bounded sequences are created by additionally specifying the maximum capacity (the `N` type /// parameter) in the beginning, followed by a `#`. diff --git a/rosidl_runtime_rs/src/traits.rs b/rosidl_runtime_rs/src/traits.rs index ae7c1df..446f4b0 100644 --- a/rosidl_runtime_rs/src/traits.rs +++ b/rosidl_runtime_rs/src/traits.rs @@ -23,8 +23,11 @@ use std::fmt::Debug; /// /// User code never needs to call these trait methods, much less implement this trait. pub trait SequenceAlloc: Sized { + /// Wraps the corresponding init function generated by `rosidl_generator_c`. fn sequence_init(seq: &mut crate::Sequence, size: libc::size_t) -> bool; + /// Wraps the corresponding fini function generated by `rosidl_generator_c`. fn sequence_fini(seq: &mut crate::Sequence); + /// Wraps the corresponding copy function generated by `rosidl_generator_c`. fn sequence_copy(in_seq: &crate::Sequence, out_seq: &mut crate::Sequence) -> bool; } @@ -35,6 +38,7 @@ pub trait SequenceAlloc: Sized { /// /// User code never needs to call this trait's method, much less implement this trait. pub trait RmwMessage: Clone + Debug + Default { + /// Get a pointer to the correct `rosidl_message_type_support_t` structure. fn get_type_support() -> libc::uintptr_t; } From 76ce7fd336c4864b64d0fd3690d0379b42c85bae Mon Sep 17 00:00:00 2001 From: Esteve Fernandez Date: Thu, 21 Apr 2022 20:55:25 +0200 Subject: [PATCH 06/40] Removed support for no_std (#109) * Removed support for no_std * Removed more no_std dependencies * Removed more no_std dependencies * Removed more no_std dependencies * Removed downcast * Removed TryFrom, not needed with Rust 2021 * Fix visibility of modules --- rosidl_runtime_rs/src/string.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/rosidl_runtime_rs/src/string.rs b/rosidl_runtime_rs/src/string.rs index 9584032..de2025c 100644 --- a/rosidl_runtime_rs/src/string.rs +++ b/rosidl_runtime_rs/src/string.rs @@ -1,5 +1,4 @@ use std::cmp::Ordering; -use std::convert::TryFrom; use std::ffi::CStr; use std::fmt::{self, Debug, Display}; use std::hash::{Hash, Hasher}; @@ -62,7 +61,6 @@ pub struct WString { /// /// ``` /// # use rosidl_runtime_rs::BoundedString; -/// # use std::convert::TryFrom; /// let mut maybe_str = BoundedString::<3>::try_from("noo!"); /// assert!(maybe_str.is_err()); /// maybe_str = BoundedString::<3>::try_from("ok!"); @@ -85,7 +83,6 @@ pub struct BoundedString { /// /// ``` /// # use rosidl_runtime_rs::BoundedWString; -/// # use std::convert::TryFrom; /// let mut maybe_wstr = BoundedWString::<3>::try_from("noo!"); /// assert!(maybe_wstr.is_err()); /// maybe_wstr = BoundedWString::<3>::try_from("ok!"); From 4e00fdcc59ba1dbb7019c8da3a0ecfb40a68c1ca Mon Sep 17 00:00:00 2001 From: Nikolai Morin Date: Thu, 28 Apr 2022 16:52:19 +0200 Subject: [PATCH 07/40] Add comments explaining each dependency (#122) --- rosidl_runtime_rs/Cargo.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rosidl_runtime_rs/Cargo.toml b/rosidl_runtime_rs/Cargo.toml index 93b09a6..3817fee 100644 --- a/rosidl_runtime_rs/Cargo.toml +++ b/rosidl_runtime_rs/Cargo.toml @@ -7,8 +7,12 @@ edition = "2021" [lib] path = "src/lib.rs" +# Please keep the list of dependencies alphabetically sorted, +# and also state why each dependency is needed. [dependencies] +# Needed for FFI libc = "0.2" [dev-dependencies] +# Needed for writing property tests quickcheck = "1" \ No newline at end of file From 4ce9e042f5199269be7f0b30692ce6114ddf8a05 Mon Sep 17 00:00:00 2001 From: Nikolai Morin Date: Fri, 29 Apr 2022 11:49:32 +0200 Subject: [PATCH 08/40] Add serde support to messages (#131) --- rosidl_runtime_rs/Cargo.toml | 8 +- rosidl_runtime_rs/src/sequence.rs | 18 +++- rosidl_runtime_rs/src/sequence/serde.rs | 70 ++++++++++++++ rosidl_runtime_rs/src/string.rs | 37 ++++++++ rosidl_runtime_rs/src/string/serde.rs | 120 ++++++++++++++++++++++++ 5 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 rosidl_runtime_rs/src/sequence/serde.rs create mode 100644 rosidl_runtime_rs/src/string/serde.rs diff --git a/rosidl_runtime_rs/Cargo.toml b/rosidl_runtime_rs/Cargo.toml index 3817fee..751f634 100644 --- a/rosidl_runtime_rs/Cargo.toml +++ b/rosidl_runtime_rs/Cargo.toml @@ -12,7 +12,13 @@ path = "src/lib.rs" [dependencies] # Needed for FFI libc = "0.2" +# Optional dependency for making it possible to convert messages to and from +# formats such as JSON, YAML, Pickle, etc. +serde = { version = "1", optional = true } [dev-dependencies] # Needed for writing property tests -quickcheck = "1" \ No newline at end of file +quickcheck = "1" +# Needed for testing serde support +serde_json = "1" + diff --git a/rosidl_runtime_rs/src/sequence.rs b/rosidl_runtime_rs/src/sequence.rs index bf31fce..16937cd 100644 --- a/rosidl_runtime_rs/src/sequence.rs +++ b/rosidl_runtime_rs/src/sequence.rs @@ -4,6 +4,9 @@ use std::hash::{Hash, Hasher}; use std::iter::{Extend, FromIterator, FusedIterator}; use std::ops::{Deref, DerefMut}; +#[cfg(feature = "serde")] +mod serde; + use crate::traits::SequenceAlloc; /// An unbounded sequence. @@ -674,7 +677,20 @@ macro_rules! seq { #[cfg(test)] mod tests { use super::*; - use quickcheck::quickcheck; + use quickcheck::{quickcheck, Arbitrary, Gen}; + + impl Arbitrary for Sequence { + fn arbitrary(g: &mut Gen) -> Self { + Vec::arbitrary(g).into() + } + } + + impl Arbitrary for BoundedSequence { + fn arbitrary(g: &mut Gen) -> Self { + let len = u8::arbitrary(g); + (0..len).map(|_| T::arbitrary(g)).collect() + } + } quickcheck! { fn test_extend(xs: Vec, ys: Vec) -> bool { diff --git a/rosidl_runtime_rs/src/sequence/serde.rs b/rosidl_runtime_rs/src/sequence/serde.rs new file mode 100644 index 0000000..367bd07 --- /dev/null +++ b/rosidl_runtime_rs/src/sequence/serde.rs @@ -0,0 +1,70 @@ +use serde::{de::Error, ser::SerializeSeq, Deserialize, Deserializer, Serialize, Serializer}; + +use super::{BoundedSequence, Sequence}; +use crate::traits::SequenceAlloc; + +impl<'de, T: Deserialize<'de> + SequenceAlloc> Deserialize<'de> for Sequence { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let v: Vec<_> = Deserialize::deserialize(deserializer)?; + Ok(Self::from(v)) + } +} + +impl Serialize for Sequence { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.len()))?; + for element in self.iter() { + seq.serialize_element(element)?; + } + seq.end() + } +} + +impl<'de, T: Deserialize<'de> + SequenceAlloc, const N: usize> Deserialize<'de> + for BoundedSequence +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let v: Vec<_> = Deserialize::deserialize(deserializer)?; + Self::try_from(v).map_err(D::Error::custom) + } +} + +impl Serialize for BoundedSequence { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.inner.serialize(serializer) + } +} + +#[cfg(test)] +mod tests { + use crate::{BoundedSequence, Sequence}; + use quickcheck::quickcheck; + + quickcheck! { + fn test_json_roundtrip_sequence(xs: Sequence) -> bool { + let value = serde_json::to_value(xs.clone()).unwrap(); + let recovered = serde_json::from_value(value).unwrap(); + xs == recovered + } + } + + quickcheck! { + fn test_json_roundtrip_bounded_sequence(xs: BoundedSequence) -> bool { + let value = serde_json::to_value(xs.clone()).unwrap(); + let recovered = serde_json::from_value(value).unwrap(); + xs == recovered + } + } +} diff --git a/rosidl_runtime_rs/src/string.rs b/rosidl_runtime_rs/src/string.rs index de2025c..83b6ddb 100644 --- a/rosidl_runtime_rs/src/string.rs +++ b/rosidl_runtime_rs/src/string.rs @@ -4,6 +4,9 @@ use std::fmt::{self, Debug, Display}; use std::hash::{Hash, Hasher}; use std::ops::{Deref, DerefMut}; +#[cfg(feature = "serde")] +mod serde; + use crate::sequence::Sequence; use crate::traits::SequenceAlloc; @@ -449,3 +452,37 @@ impl Display for StringExceedsBoundsError { } impl std::error::Error for StringExceedsBoundsError {} + +#[cfg(test)] +mod tests { + use super::*; + use quickcheck::{Arbitrary, Gen}; + + impl Arbitrary for String { + fn arbitrary(g: &mut Gen) -> Self { + std::string::String::arbitrary(g).as_str().into() + } + } + + impl Arbitrary for WString { + fn arbitrary(g: &mut Gen) -> Self { + std::string::String::arbitrary(g).as_str().into() + } + } + + impl Arbitrary for BoundedString<256> { + fn arbitrary(g: &mut Gen) -> Self { + let len = u8::arbitrary(g); + let s: std::string::String = (0..len).map(|_| char::arbitrary(g)).collect(); + s.as_str().try_into().unwrap() + } + } + + impl Arbitrary for BoundedWString<256> { + fn arbitrary(g: &mut Gen) -> Self { + let len = u8::arbitrary(g); + let s: std::string::String = (0..len).map(|_| char::arbitrary(g)).collect(); + s.as_str().try_into().unwrap() + } + } +} diff --git a/rosidl_runtime_rs/src/string/serde.rs b/rosidl_runtime_rs/src/string/serde.rs new file mode 100644 index 0000000..b0166a1 --- /dev/null +++ b/rosidl_runtime_rs/src/string/serde.rs @@ -0,0 +1,120 @@ +use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; +use std::ops::Deref; + +use super::{BoundedString, BoundedWString, String, WString}; + +impl<'de> Deserialize<'de> for String { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + std::string::String::deserialize(deserializer).map(|s| Self::from(s.as_str())) + } +} + +impl Serialize for String { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + // Not particularly efficient + let s = std::string::String::from_utf8_lossy(self.deref()); + serializer.serialize_str(&s) + } +} + +impl<'de> Deserialize<'de> for WString { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + std::string::String::deserialize(deserializer).map(|s| Self::from(s.as_str())) + } +} + +impl Serialize for WString { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + // Not particularly efficient + let s = std::string::String::from_utf16_lossy(self.deref()); + serializer.serialize_str(&s) + } +} + +impl<'de, const N: usize> Deserialize<'de> for BoundedString { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + std::string::String::deserialize(deserializer) + .and_then(|s| Self::try_from(s.as_str()).map_err(D::Error::custom)) + } +} + +impl Serialize for BoundedString { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.inner.serialize(serializer) + } +} + +impl<'de, const N: usize> Deserialize<'de> for BoundedWString { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + std::string::String::deserialize(deserializer) + .and_then(|s| Self::try_from(s.as_str()).map_err(D::Error::custom)) + } +} + +impl Serialize for BoundedWString { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.inner.serialize(serializer) + } +} + +#[cfg(test)] +mod tests { + use crate::{BoundedString, BoundedWString, String, WString}; + use quickcheck::quickcheck; + + quickcheck! { + fn test_json_roundtrip_string(s: String) -> bool { + let value = serde_json::to_value(s.clone()).unwrap(); + let recovered = serde_json::from_value(value).unwrap(); + s == recovered + } + } + + quickcheck! { + fn test_json_roundtrip_wstring(s: WString) -> bool { + let value = serde_json::to_value(s.clone()).unwrap(); + let recovered = serde_json::from_value(value).unwrap(); + s == recovered + } + } + + quickcheck! { + fn test_json_roundtrip_bounded_string(s: BoundedString<256>) -> bool { + let value = serde_json::to_value(s.clone()).unwrap(); + let recovered = serde_json::from_value(value).unwrap(); + s == recovered + } + } + + quickcheck! { + fn test_json_roundtrip_bounded_wstring(s: BoundedWString<256>) -> bool { + let value = serde_json::to_value(s.clone()).unwrap(); + let recovered = serde_json::from_value(value).unwrap(); + s == recovered + } + } +} From 48b0247a2474059e114416f701c741658fee9225 Mon Sep 17 00:00:00 2001 From: Nikolai Morin Date: Thu, 12 May 2022 10:13:23 +0200 Subject: [PATCH 09/40] Rename RMW-compatible to RMW-native (#159) --- rosidl_runtime_rs/src/traits.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/rosidl_runtime_rs/src/traits.rs b/rosidl_runtime_rs/src/traits.rs index 446f4b0..c9c2c59 100644 --- a/rosidl_runtime_rs/src/traits.rs +++ b/rosidl_runtime_rs/src/traits.rs @@ -31,7 +31,7 @@ pub trait SequenceAlloc: Sized { fn sequence_copy(in_seq: &crate::Sequence, out_seq: &mut crate::Sequence) -> bool; } -/// Trait for RMW-compatible messages. +/// Trait for RMW-native messages. /// /// See the documentation for the [`Message`] trait, which is the trait that should generally be /// used by user code. @@ -46,22 +46,22 @@ pub trait RmwMessage: Clone + Debug + Default { /// /// `rosidl_generator_rs` generates two types of messages that implement this trait: /// - An "idiomatic" message type, in the `${package_name}::msg` module -/// - An "RMW-compatible" message type, in the `${package_name}::msg::rmw` module +/// - An "RMW-native" message type, in the `${package_name}::msg::rmw` module /// /// # Idiomatic message type /// The idiomatic message type aims to be familiar to Rust developers and ROS 2 developers coming /// from `rclcpp`. /// To this end, it translates the original ROS 2 message into a version that uses idiomatic Rust /// structs: [`std::vec::Vec`] for sequences and [`std::string::String`] for strings. All other -/// fields are the same as in an RMW-compatible message. +/// fields are the same as in an RMW-native message. /// /// This conversion incurs some overhead when reading and publishing messages. /// -/// It's possible to use the idiomatic type for a publisher and the RMW-compatible type for a +/// It's possible to use the idiomatic type for a publisher and the RMW-native type for a /// corresponding subscription, and vice versa. /// -/// # RMW-compatible message type -/// The RMW-compatible message type aims to achieve higher performance by avoiding the conversion +/// # RMW-native message type +/// The RMW-native message type aims to achieve higher performance by avoiding the conversion /// step to an idiomatic message. /// /// It uses the following type mapping: @@ -88,7 +88,7 @@ pub trait RmwMessage: Clone + Debug + Default { /// iteration and all of the functionality of slices. However, it doesn't have an equivalent of /// [`Vec::push()`], among others. /// -/// ## What does "RMW-compatible" mean in detail? +/// ## What does "RMW-native" mean in detail? /// The message can be directly passed to and from the RMW layer because (1) its layout is /// identical to the layout of the type generated by `rosidl_generator_c` and (2) the dynamic /// memory inside the message is owned by the C allocator. @@ -127,19 +127,19 @@ pub trait RmwMessage: Clone + Debug + Default { /// The `Drop` impl for any sequence or string will call `fini()`. pub trait Message: Clone + Debug + Default + 'static { - /// The corresponding RMW-compatible message type. + /// The corresponding RMW-native message type. type RmwMsg: RmwMessage; - /// Converts the idiomatic message into an RMW-compatible message. + /// Converts the idiomatic message into an RMW-native message. /// /// If the idiomatic message is owned, a slightly more efficient conversion is possible. /// This is why the function takes a `Cow`. /// - /// If this function receives a borrowed message that is already RMW-compatible, it should + /// If this function receives a borrowed message that is already RMW-native, it should /// directly return that borrowed message. /// This is why the return type is also `Cow`. fn into_rmw_message(msg_cow: Cow<'_, Self>) -> Cow<'_, Self::RmwMsg>; - /// Converts the RMW-compatible message into an idiomatic message. + /// Converts the RMW-native message into an idiomatic message. fn from_rmw_message(msg: Self::RmwMsg) -> Self; } From f87dd7b399a07bce2aefa7848c98bf79a9d171d4 Mon Sep 17 00:00:00 2001 From: Nikolai Morin Date: Fri, 20 May 2022 13:34:00 +0200 Subject: [PATCH 10/40] Make all the things Send, and messages Sync as well (#171) This is required to make multithreading work. For instance, calling publish() on a separate thread doesn't work without these impls. --- rosidl_runtime_rs/src/sequence.rs | 5 +++++ rosidl_runtime_rs/src/string.rs | 5 +++++ rosidl_runtime_rs/src/traits.rs | 4 ++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/rosidl_runtime_rs/src/sequence.rs b/rosidl_runtime_rs/src/sequence.rs index 16937cd..e4574db 100644 --- a/rosidl_runtime_rs/src/sequence.rs +++ b/rosidl_runtime_rs/src/sequence.rs @@ -235,6 +235,11 @@ impl PartialOrd for Sequence { } } +// SAFETY: A sequence is a simple data structure, and therefore not thread-specific. +unsafe impl Send for Sequence {} +// SAFETY: A sequence does not have interior mutability, so it can be shared. +unsafe impl Sync for Sequence {} + impl Sequence where T: SequenceAlloc, diff --git a/rosidl_runtime_rs/src/string.rs b/rosidl_runtime_rs/src/string.rs index 83b6ddb..e3c2898 100644 --- a/rosidl_runtime_rs/src/string.rs +++ b/rosidl_runtime_rs/src/string.rs @@ -213,6 +213,11 @@ macro_rules! string_impl { } } + // SAFETY: A string is a simple data structure, and therefore not thread-specific. + unsafe impl Send for $string {} + // SAFETY: A string does not have interior mutability, so it can be shared. + unsafe impl Sync for $string {} + impl SequenceAlloc for $string { fn sequence_init(seq: &mut Sequence, size: libc::size_t) -> bool { // SAFETY: There are no special preconditions to the sequence_init function. diff --git a/rosidl_runtime_rs/src/traits.rs b/rosidl_runtime_rs/src/traits.rs index c9c2c59..21d7c32 100644 --- a/rosidl_runtime_rs/src/traits.rs +++ b/rosidl_runtime_rs/src/traits.rs @@ -37,7 +37,7 @@ pub trait SequenceAlloc: Sized { /// used by user code. /// /// User code never needs to call this trait's method, much less implement this trait. -pub trait RmwMessage: Clone + Debug + Default { +pub trait RmwMessage: Clone + Debug + Default + Send + Sync { /// Get a pointer to the correct `rosidl_message_type_support_t` structure. fn get_type_support() -> libc::uintptr_t; } @@ -126,7 +126,7 @@ pub trait RmwMessage: Clone + Debug + Default { /// problem, since nothing is allocated this way. /// The `Drop` impl for any sequence or string will call `fini()`. -pub trait Message: Clone + Debug + Default + 'static { +pub trait Message: Clone + Debug + Default + 'static + Send + Sync { /// The corresponding RMW-native message type. type RmwMsg: RmwMessage; From 9aab53a106a3444f7256ebb49afe0771d50ca592 Mon Sep 17 00:00:00 2001 From: Nikolai Morin Date: Mon, 23 May 2022 14:59:21 +0200 Subject: [PATCH 11/40] Add Nikolai and Jacob to authors in Cargo.toml and maintainers in package.xml (#133) --- rosidl_runtime_rs/Cargo.toml | 3 ++- rosidl_runtime_rs/package.xml | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/rosidl_runtime_rs/Cargo.toml b/rosidl_runtime_rs/Cargo.toml index 751f634..82bf5bd 100644 --- a/rosidl_runtime_rs/Cargo.toml +++ b/rosidl_runtime_rs/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "rosidl_runtime_rs" version = "0.2.0" -authors = ["Jacob Hassold ", "Nikolai Morin "] +# This project is not military-sponsored, Jacob's employment contract just requires him to use this email address +authors = ["Jacob Hassold ", "Nikolai Morin "] edition = "2021" [lib] diff --git a/rosidl_runtime_rs/package.xml b/rosidl_runtime_rs/package.xml index b9aa65c..4de3094 100644 --- a/rosidl_runtime_rs/package.xml +++ b/rosidl_runtime_rs/package.xml @@ -6,10 +6,11 @@ rosidl_runtime_rs 0.2.0 Message generation code shared by Rust projects in ROS 2 - Jacob Hassold + + Jacob Hassold Nikolai Morin Apache License 2.0 - Jacob Hassold + Jacob Hassold Nikolai Morin rosidl_runtime_c From 38be820cafe38c2a8586b112f07c7f3407236f04 Mon Sep 17 00:00:00 2001 From: Esteve Fernandez Date: Tue, 14 Jun 2022 19:29:11 +0200 Subject: [PATCH 12/40] Add unit testing (#84) * Copied tests from jhdcs' fork * Ran cargo fmt * Run clippy on tests as well * Make Clippy happy * Fix rustfmt warning * Added std_msgs to package.xml * Disable deref_nullptr warning for generated code * Include Soya-Onishi's changes * Fix Node::new call * Fix spelling mistakes Co-authored-by: jhdcs <48914066+jhdcs@users.noreply.github.com> * Fix spelling mistakes Co-authored-by: jhdcs <48914066+jhdcs@users.noreply.github.com> * Fix spelling mistakes Co-authored-by: jhdcs <48914066+jhdcs@users.noreply.github.com> * Fix formatting Co-authored-by: jhdcs <48914066+jhdcs@users.noreply.github.com> --- rosidl_runtime_rs/src/sequence.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rosidl_runtime_rs/src/sequence.rs b/rosidl_runtime_rs/src/sequence.rs index e4574db..dd4c7c8 100644 --- a/rosidl_runtime_rs/src/sequence.rs +++ b/rosidl_runtime_rs/src/sequence.rs @@ -705,10 +705,10 @@ mod tests { if xs_seq.len() != xs.len() + ys.len() { return false; } - if &xs_seq[..xs.len()] != &xs[..] { + if xs_seq[..xs.len()] != xs[..] { return false; } - if &xs_seq[xs.len()..] != &ys[..] { + if xs_seq[xs.len()..] != ys[..] { return false; } true From fe2f93c4f64b090c7c6edaf6943ce6e0c4256035 Mon Sep 17 00:00:00 2001 From: Nikolai Morin Date: Fri, 8 Jul 2022 11:45:41 +0200 Subject: [PATCH 13/40] Fix a portability problem in rosidl_runtime_rs::String (#219) Previously, it was assumed by the $string_conversion_func, for instance, that the output of deref() would be an unsigned integer. --- rosidl_runtime_rs/src/string.rs | 17 ++++++++++++----- rosidl_runtime_rs/src/string/serde.rs | 9 ++++++--- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/rosidl_runtime_rs/src/string.rs b/rosidl_runtime_rs/src/string.rs index e3c2898..fc85907 100644 --- a/rosidl_runtime_rs/src/string.rs +++ b/rosidl_runtime_rs/src/string.rs @@ -110,7 +110,7 @@ pub struct StringExceedsBoundsError { // There is a lot of redundancy between String and WString, which this macro aims to reduce. macro_rules! string_impl { - ($string:ty, $char_type:ty, $string_conversion_func:ident, $init:ident, $fini:ident, $assignn:ident, $sequence_init:ident, $sequence_fini:ident, $sequence_copy:ident) => { + ($string:ty, $char_type:ty, $unsigned_char_type:ty, $string_conversion_func:ident, $init:ident, $fini:ident, $assignn:ident, $sequence_init:ident, $sequence_fini:ident, $sequence_copy:ident) => { #[link(name = "rosidl_runtime_c")] extern "C" { fn $init(s: *mut $string) -> bool; @@ -159,7 +159,7 @@ macro_rules! string_impl { fn deref(&self) -> &Self::Target { // SAFETY: self.data points to self.size consecutive, initialized elements and // isn't modified externally. - unsafe { std::slice::from_raw_parts(self.data as *const $char_type, self.size) } + unsafe { std::slice::from_raw_parts(self.data, self.size) } } } @@ -167,13 +167,18 @@ macro_rules! string_impl { fn deref_mut(&mut self) -> &mut Self::Target { // SAFETY: self.data points to self.size consecutive, initialized elements and // isn't modified externally. - unsafe { std::slice::from_raw_parts_mut(self.data as *mut $char_type, self.size) } + unsafe { std::slice::from_raw_parts_mut(self.data, self.size) } } } impl Display for $string { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - let converted = std::string::String::$string_conversion_func(self.deref()); + // SAFETY: Same as deref, but with an additional cast to the unsigned type. + // See also https://users.rust-lang.org/t/how-to-convert-i8-to-u8/16308/11 + let u8_slice = unsafe { + std::slice::from_raw_parts(self.data as *mut $unsigned_char_type, self.size) + }; + let converted = std::string::String::$string_conversion_func(u8_slice); Display::fmt(&converted, f) } } @@ -238,6 +243,7 @@ macro_rules! string_impl { string_impl!( String, + libc::c_char, u8, from_utf8_lossy, rosidl_runtime_c__String__init, @@ -250,6 +256,7 @@ string_impl!( string_impl!( WString, libc::c_ushort, + u16, from_utf16_lossy, rosidl_runtime_c__U16String__init, rosidl_runtime_c__U16String__fini, @@ -321,7 +328,7 @@ impl Debug for BoundedString { } impl Deref for BoundedString { - type Target = [u8]; + type Target = [libc::c_char]; fn deref(&self) -> &Self::Target { self.inner.deref() } diff --git a/rosidl_runtime_rs/src/string/serde.rs b/rosidl_runtime_rs/src/string/serde.rs index b0166a1..36f3b1e 100644 --- a/rosidl_runtime_rs/src/string/serde.rs +++ b/rosidl_runtime_rs/src/string/serde.rs @@ -1,5 +1,4 @@ use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; -use std::ops::Deref; use super::{BoundedString, BoundedWString, String, WString}; @@ -18,7 +17,9 @@ impl Serialize for String { S: Serializer, { // Not particularly efficient - let s = std::string::String::from_utf8_lossy(self.deref()); + // SAFETY: See the Display implementation. + let u8_slice = unsafe { std::slice::from_raw_parts(self.data as *mut u8, self.size) }; + let s = std::string::String::from_utf8_lossy(u8_slice); serializer.serialize_str(&s) } } @@ -38,7 +39,9 @@ impl Serialize for WString { S: Serializer, { // Not particularly efficient - let s = std::string::String::from_utf16_lossy(self.deref()); + // SAFETY: See the Display implementation. + let u16_slice = unsafe { std::slice::from_raw_parts(self.data as *mut u16, self.size) }; + let s = std::string::String::from_utf16_lossy(u16_slice); serializer.serialize_str(&s) } } From 2665187a36abbf85835a496fe1561abd568cbc70 Mon Sep 17 00:00:00 2001 From: Esteve Fernandez Date: Fri, 8 Jul 2022 14:43:36 +0200 Subject: [PATCH 14/40] Added support for clients and services (#146) * Added support for clients and services --- rosidl_runtime_rs/src/lib.rs | 2 +- rosidl_runtime_rs/src/traits.rs | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/rosidl_runtime_rs/src/lib.rs b/rosidl_runtime_rs/src/lib.rs index 9cc8076..93f8441 100644 --- a/rosidl_runtime_rs/src/lib.rs +++ b/rosidl_runtime_rs/src/lib.rs @@ -9,4 +9,4 @@ mod string; pub use string::{BoundedString, BoundedWString, String, StringExceedsBoundsError, WString}; mod traits; -pub use traits::{Message, RmwMessage, SequenceAlloc}; +pub use traits::{Message, RmwMessage, SequenceAlloc, Service}; diff --git a/rosidl_runtime_rs/src/traits.rs b/rosidl_runtime_rs/src/traits.rs index 21d7c32..86e9746 100644 --- a/rosidl_runtime_rs/src/traits.rs +++ b/rosidl_runtime_rs/src/traits.rs @@ -143,3 +143,17 @@ pub trait Message: Clone + Debug + Default + 'static + Send + Sync { /// Converts the RMW-native message into an idiomatic message. fn from_rmw_message(msg: Self::RmwMsg) -> Self; } + +/// Trait for services. +/// +/// User code never needs to call this trait's method, much less implement this trait. +pub trait Service: 'static { + /// The request message associated with this service. + type Request: Message; + + /// The response message associated with this service. + type Response: Message; + + /// Get a pointer to the correct `rosidl_service_type_support_t` structure. + fn get_type_support() -> libc::uintptr_t; +} From 3ae7d35d9b50b1db614d448cee8ade0b6f7de47c Mon Sep 17 00:00:00 2001 From: Esteve Fernandez Date: Thu, 21 Jul 2022 19:38:57 +0200 Subject: [PATCH 15/40] Fixes for releasing to crates.io (#231) * Fixups for releasing to crates.io * Removed std_msgs as test dependency. Fix rosidl_runtime_rs version * Removed test * Removed test --- rosidl_runtime_rs/Cargo.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rosidl_runtime_rs/Cargo.toml b/rosidl_runtime_rs/Cargo.toml index 82bf5bd..0295468 100644 --- a/rosidl_runtime_rs/Cargo.toml +++ b/rosidl_runtime_rs/Cargo.toml @@ -2,8 +2,10 @@ name = "rosidl_runtime_rs" version = "0.2.0" # This project is not military-sponsored, Jacob's employment contract just requires him to use this email address -authors = ["Jacob Hassold ", "Nikolai Morin "] +authors = ["Esteve Fernandez ", "Nikolai Morin ", "Jacob Hassold "] edition = "2021" +license = "Apache-2.0" +description = "Message generation code shared by Rust projects in ROS 2" [lib] path = "src/lib.rs" From e0f83cf642953f6b055e71bc9144825128336360 Mon Sep 17 00:00:00 2001 From: Raghav Mishra Date: Mon, 1 Aug 2022 22:07:10 +1000 Subject: [PATCH 16/40] Implement Message for Box (#235) --- rosidl_runtime_rs/src/traits.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/rosidl_runtime_rs/src/traits.rs b/rosidl_runtime_rs/src/traits.rs index 86e9746..ff19399 100644 --- a/rosidl_runtime_rs/src/traits.rs +++ b/rosidl_runtime_rs/src/traits.rs @@ -144,6 +144,20 @@ pub trait Message: Clone + Debug + Default + 'static + Send + Sync { fn from_rmw_message(msg: Self::RmwMsg) -> Self; } +/// Allows boxed values to be used by users in Publishers and Subscriptions. +// See https://github.com/ros2-rust/ros2_rust/pull/235 for a discussion of possible alternatives. +impl Message for Box { + type RmwMsg = T::RmwMsg; + + fn into_rmw_message(msg_cow: Cow<'_, Self>) -> Cow<'_, Self::RmwMsg> { + T::into_rmw_message(Cow::Owned(*msg_cow.into_owned())) + } + + fn from_rmw_message(msg: Self::RmwMsg) -> Self { + Box::new(T::from_rmw_message(msg)) + } +} + /// Trait for services. /// /// User code never needs to call this trait's method, much less implement this trait. From 0d72d2639605692ab2c371a015ce02e8d327b074 Mon Sep 17 00:00:00 2001 From: Nikolai Morin Date: Tue, 16 Aug 2022 15:43:52 +0200 Subject: [PATCH 17/40] Add support for loaned messages (#212) --- rosidl_runtime_rs/src/traits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rosidl_runtime_rs/src/traits.rs b/rosidl_runtime_rs/src/traits.rs index ff19399..a39bcff 100644 --- a/rosidl_runtime_rs/src/traits.rs +++ b/rosidl_runtime_rs/src/traits.rs @@ -37,7 +37,7 @@ pub trait SequenceAlloc: Sized { /// used by user code. /// /// User code never needs to call this trait's method, much less implement this trait. -pub trait RmwMessage: Clone + Debug + Default + Send + Sync { +pub trait RmwMessage: Clone + Debug + Default + Send + Sync + Message { /// Get a pointer to the correct `rosidl_message_type_support_t` structure. fn get_type_support() -> libc::uintptr_t; } From e7d13b54af57245e2085cc3484d142030c376845 Mon Sep 17 00:00:00 2001 From: Nikolai Morin Date: Tue, 20 Sep 2022 09:23:33 +0200 Subject: [PATCH 18/40] Generalize callbacks for subscriptions (#260) * Generalize callbacks to subscriptions By implementing a trait (SubscriptionCallback) on valid function signatures, and making subscriptions accept callbacks that implement this trait, we can now * Receive both plain and boxed messages * Optionally receive a MessageInfo along with the message, as the second argument * Soon, receive a loaned message instead of an owned one This corresponds to the functionality in any_subscription_callback.hpp in rclcpp. --- rosidl_runtime_rs/src/traits.rs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/rosidl_runtime_rs/src/traits.rs b/rosidl_runtime_rs/src/traits.rs index a39bcff..dc03fef 100644 --- a/rosidl_runtime_rs/src/traits.rs +++ b/rosidl_runtime_rs/src/traits.rs @@ -144,20 +144,6 @@ pub trait Message: Clone + Debug + Default + 'static + Send + Sync { fn from_rmw_message(msg: Self::RmwMsg) -> Self; } -/// Allows boxed values to be used by users in Publishers and Subscriptions. -// See https://github.com/ros2-rust/ros2_rust/pull/235 for a discussion of possible alternatives. -impl Message for Box { - type RmwMsg = T::RmwMsg; - - fn into_rmw_message(msg_cow: Cow<'_, Self>) -> Cow<'_, Self::RmwMsg> { - T::into_rmw_message(Cow::Owned(*msg_cow.into_owned())) - } - - fn from_rmw_message(msg: Self::RmwMsg) -> Self { - Box::new(T::from_rmw_message(msg)) - } -} - /// Trait for services. /// /// User code never needs to call this trait's method, much less implement this trait. From 58509293326c4a65e0ead39ea0f8272d475d8da7 Mon Sep 17 00:00:00 2001 From: Nikolai Morin Date: Mon, 3 Oct 2022 15:39:39 +0200 Subject: [PATCH 19/40] Bump package versions to 0.3 (#274) --- rosidl_runtime_rs/Cargo.toml | 2 +- rosidl_runtime_rs/package.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rosidl_runtime_rs/Cargo.toml b/rosidl_runtime_rs/Cargo.toml index 0295468..1897650 100644 --- a/rosidl_runtime_rs/Cargo.toml +++ b/rosidl_runtime_rs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rosidl_runtime_rs" -version = "0.2.0" +version = "0.3.0" # This project is not military-sponsored, Jacob's employment contract just requires him to use this email address authors = ["Esteve Fernandez ", "Nikolai Morin ", "Jacob Hassold "] edition = "2021" diff --git a/rosidl_runtime_rs/package.xml b/rosidl_runtime_rs/package.xml index 4de3094..6da7581 100644 --- a/rosidl_runtime_rs/package.xml +++ b/rosidl_runtime_rs/package.xml @@ -4,7 +4,7 @@ schematypens="http://www.w3.org/2001/XMLSchema"?> rosidl_runtime_rs - 0.2.0 + 0.3.0 Message generation code shared by Rust projects in ROS 2 Jacob Hassold From 31341a682c8261e4c5a04b6d22e05e34d393aa76 Mon Sep 17 00:00:00 2001 From: Nikolai Morin Date: Mon, 3 Oct 2022 15:40:13 +0200 Subject: [PATCH 20/40] Add README files for rclrs and rosidl_runtime_rs (#273) These files will be shown by crates.io. --- rosidl_runtime_rs/README.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 rosidl_runtime_rs/README.md diff --git a/rosidl_runtime_rs/README.md b/rosidl_runtime_rs/README.md new file mode 100644 index 0000000..e391a4c --- /dev/null +++ b/rosidl_runtime_rs/README.md @@ -0,0 +1,5 @@ +# Common types and traits for ROS 2 messages in Rust + +ROS 2 is a popular open source robotics framework, used in a variety of fields (self-driving cars, drones, humanoid robots, etc.). `rosidl_runtime_rs` is a library that is mainly used by generated code for ROS 2 messages. + +Please see the docs in the [`ros2_rust` repo](https://github.com/ros2-rust/ros2_rust). \ No newline at end of file From 6a0d9190721c3362eaeb8cbc757e58fc2e4561aa Mon Sep 17 00:00:00 2001 From: Nikolai Morin Date: Mon, 3 Oct 2022 17:03:14 +0200 Subject: [PATCH 21/40] Format all code with group_imports = StdExternalCrate (#272) --- rosidl_runtime_rs/src/sequence.rs | 3 ++- rosidl_runtime_rs/src/sequence/serde.rs | 3 ++- rosidl_runtime_rs/src/string.rs | 3 ++- rosidl_runtime_rs/src/string/serde.rs | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/rosidl_runtime_rs/src/sequence.rs b/rosidl_runtime_rs/src/sequence.rs index dd4c7c8..acef786 100644 --- a/rosidl_runtime_rs/src/sequence.rs +++ b/rosidl_runtime_rs/src/sequence.rs @@ -681,9 +681,10 @@ macro_rules! seq { #[cfg(test)] mod tests { - use super::*; use quickcheck::{quickcheck, Arbitrary, Gen}; + use super::*; + impl Arbitrary for Sequence { fn arbitrary(g: &mut Gen) -> Self { Vec::arbitrary(g).into() diff --git a/rosidl_runtime_rs/src/sequence/serde.rs b/rosidl_runtime_rs/src/sequence/serde.rs index 367bd07..f3af605 100644 --- a/rosidl_runtime_rs/src/sequence/serde.rs +++ b/rosidl_runtime_rs/src/sequence/serde.rs @@ -49,9 +49,10 @@ impl Serialize for BoundedSequence #[cfg(test)] mod tests { - use crate::{BoundedSequence, Sequence}; use quickcheck::quickcheck; + use crate::{BoundedSequence, Sequence}; + quickcheck! { fn test_json_roundtrip_sequence(xs: Sequence) -> bool { let value = serde_json::to_value(xs.clone()).unwrap(); diff --git a/rosidl_runtime_rs/src/string.rs b/rosidl_runtime_rs/src/string.rs index fc85907..e3f5ab6 100644 --- a/rosidl_runtime_rs/src/string.rs +++ b/rosidl_runtime_rs/src/string.rs @@ -467,9 +467,10 @@ impl std::error::Error for StringExceedsBoundsError {} #[cfg(test)] mod tests { - use super::*; use quickcheck::{Arbitrary, Gen}; + use super::*; + impl Arbitrary for String { fn arbitrary(g: &mut Gen) -> Self { std::string::String::arbitrary(g).as_str().into() diff --git a/rosidl_runtime_rs/src/string/serde.rs b/rosidl_runtime_rs/src/string/serde.rs index 36f3b1e..2df52d5 100644 --- a/rosidl_runtime_rs/src/string/serde.rs +++ b/rosidl_runtime_rs/src/string/serde.rs @@ -86,9 +86,10 @@ impl Serialize for BoundedWString { #[cfg(test)] mod tests { - use crate::{BoundedString, BoundedWString, String, WString}; use quickcheck::quickcheck; + use crate::{BoundedString, BoundedWString, String, WString}; + quickcheck! { fn test_json_roundtrip_string(s: String) -> bool { let value = serde_json::to_value(s.clone()).unwrap(); From 3503d17d362a62827c315a76464cc5fbb44d25ac Mon Sep 17 00:00:00 2001 From: Nikolai Morin Date: Wed, 5 Oct 2022 13:48:12 +0200 Subject: [PATCH 22/40] Add TYPE_NAME constant to messages and make error fields public (#277) --- rosidl_runtime_rs/src/sequence.rs | 6 ++++-- rosidl_runtime_rs/src/string.rs | 6 ++++-- rosidl_runtime_rs/src/traits.rs | 3 +++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/rosidl_runtime_rs/src/sequence.rs b/rosidl_runtime_rs/src/sequence.rs index acef786..64b5892 100644 --- a/rosidl_runtime_rs/src/sequence.rs +++ b/rosidl_runtime_rs/src/sequence.rs @@ -75,8 +75,10 @@ pub struct BoundedSequence { /// Error type for [`BoundedSequence::try_new()`]. #[derive(Debug)] pub struct SequenceExceedsBoundsError { - len: usize, - upper_bound: usize, + /// The actual length the sequence would have after the operation. + pub len: usize, + /// The upper bound on the sequence length. + pub upper_bound: usize, } /// A by-value iterator created by [`Sequence::into_iter()`] and [`BoundedSequence::into_iter()`]. diff --git a/rosidl_runtime_rs/src/string.rs b/rosidl_runtime_rs/src/string.rs index e3f5ab6..305062a 100644 --- a/rosidl_runtime_rs/src/string.rs +++ b/rosidl_runtime_rs/src/string.rs @@ -102,8 +102,10 @@ pub struct BoundedWString { /// Error type for [`BoundedString::try_from()`] and [`BoundedWString::try_from()`]. #[derive(Debug)] pub struct StringExceedsBoundsError { - len: usize, - upper_bound: usize, + /// The actual length the string would have after the operation. + pub len: usize, + /// The upper bound on the string length. + pub upper_bound: usize, } // ========================= impls for String and WString ========================= diff --git a/rosidl_runtime_rs/src/traits.rs b/rosidl_runtime_rs/src/traits.rs index dc03fef..4737bd1 100644 --- a/rosidl_runtime_rs/src/traits.rs +++ b/rosidl_runtime_rs/src/traits.rs @@ -38,6 +38,9 @@ pub trait SequenceAlloc: Sized { /// /// User code never needs to call this trait's method, much less implement this trait. pub trait RmwMessage: Clone + Debug + Default + Send + Sync + Message { + /// A string representation of this message's type, e.g. "geometry_msgs/msg/Twist" + const TYPE_NAME: &'static str; + /// Get a pointer to the correct `rosidl_message_type_support_t` structure. fn get_type_support() -> libc::uintptr_t; } From 991d3e1c132d96f6f0348b5cb3c5601f73fb5121 Mon Sep 17 00:00:00 2001 From: Nikolai Morin Date: Mon, 17 Oct 2022 10:50:22 +0200 Subject: [PATCH 23/40] Version 0.3.1 (#285) --- rosidl_runtime_rs/Cargo.toml | 2 +- rosidl_runtime_rs/package.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rosidl_runtime_rs/Cargo.toml b/rosidl_runtime_rs/Cargo.toml index 1897650..249043e 100644 --- a/rosidl_runtime_rs/Cargo.toml +++ b/rosidl_runtime_rs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rosidl_runtime_rs" -version = "0.3.0" +version = "0.3.1" # This project is not military-sponsored, Jacob's employment contract just requires him to use this email address authors = ["Esteve Fernandez ", "Nikolai Morin ", "Jacob Hassold "] edition = "2021" diff --git a/rosidl_runtime_rs/package.xml b/rosidl_runtime_rs/package.xml index 6da7581..71bb5db 100644 --- a/rosidl_runtime_rs/package.xml +++ b/rosidl_runtime_rs/package.xml @@ -4,7 +4,7 @@ schematypens="http://www.w3.org/2001/XMLSchema"?> rosidl_runtime_rs - 0.3.0 + 0.3.1 Message generation code shared by Rust projects in ROS 2 Jacob Hassold From 625eed0d0d0421c96bc1f29318814c85b5550d4d Mon Sep 17 00:00:00 2001 From: Tatsuro Sakaguchi Date: Wed, 19 Oct 2022 03:16:18 +0900 Subject: [PATCH 24/40] Remove libc dependencies (#284) --- rosidl_runtime_rs/Cargo.toml | 3 -- rosidl_runtime_rs/src/sequence.rs | 58 ++++++------------------------- rosidl_runtime_rs/src/string.rs | 37 +++++++++++--------- rosidl_runtime_rs/src/traits.rs | 6 ++-- 4 files changed, 33 insertions(+), 71 deletions(-) diff --git a/rosidl_runtime_rs/Cargo.toml b/rosidl_runtime_rs/Cargo.toml index 249043e..e15ef0f 100644 --- a/rosidl_runtime_rs/Cargo.toml +++ b/rosidl_runtime_rs/Cargo.toml @@ -13,8 +13,6 @@ path = "src/lib.rs" # Please keep the list of dependencies alphabetically sorted, # and also state why each dependency is needed. [dependencies] -# Needed for FFI -libc = "0.2" # Optional dependency for making it possible to convert messages to and from # formats such as JSON, YAML, Pickle, etc. serde = { version = "1", optional = true } @@ -24,4 +22,3 @@ serde = { version = "1", optional = true } quickcheck = "1" # Needed for testing serde support serde_json = "1" - diff --git a/rosidl_runtime_rs/src/sequence.rs b/rosidl_runtime_rs/src/sequence.rs index 64b5892..3ac2127 100644 --- a/rosidl_runtime_rs/src/sequence.rs +++ b/rosidl_runtime_rs/src/sequence.rs @@ -37,8 +37,8 @@ use crate::traits::SequenceAlloc; #[repr(C)] pub struct Sequence { data: *mut T, - size: libc::size_t, - capacity: libc::size_t, + size: usize, + capacity: usize, } /// A bounded sequence. @@ -274,30 +274,6 @@ where } } -impl Sequence { - /// Internal function for the sequence_copy impl. To be removed when rosidl#650 is backported and released. - pub fn resize_to_at_least(&mut self, len: usize) { - let allocation_size = std::mem::size_of::() * len; - if self.capacity < len { - // SAFETY: The memory in self.data is owned by C. - let data = unsafe { libc::realloc(self.data as *mut _, allocation_size) } as *mut T; - if data.is_null() { - panic!("realloc failed"); - } - // Initialize the new memory - for i in self.capacity..len { - // SAFETY: i is in bounds, and write() is appropriate for initializing uninitialized memory - unsafe { - data.add(i).write(T::default()); - } - } - self.data = data; - self.size = len; - self.capacity = len; - } - } -} - // ========================= impl for BoundedSequence ========================= impl Debug for BoundedSequence { @@ -518,12 +494,16 @@ macro_rules! impl_sequence_alloc_for_primitive_type { ($rust_type:ty, $init_func:ident, $fini_func:ident, $copy_func:ident) => { #[link(name = "rosidl_runtime_c")] extern "C" { - fn $init_func(seq: *mut Sequence<$rust_type>, size: libc::size_t) -> bool; + fn $init_func(seq: *mut Sequence<$rust_type>, size: usize) -> bool; fn $fini_func(seq: *mut Sequence<$rust_type>); + fn $copy_func( + in_seq: *const Sequence<$rust_type>, + out_seq: *mut Sequence<$rust_type>, + ) -> bool; } impl SequenceAlloc for $rust_type { - fn sequence_init(seq: &mut Sequence, size: libc::size_t) -> bool { + fn sequence_init(seq: &mut Sequence, size: usize) -> bool { // SAFETY: There are no special preconditions to the sequence_init function. unsafe { // This allocates space and sets seq.size and seq.capacity to size @@ -538,26 +518,8 @@ macro_rules! impl_sequence_alloc_for_primitive_type { unsafe { $fini_func(seq as *mut _) } } fn sequence_copy(in_seq: &Sequence, out_seq: &mut Sequence) -> bool { - let allocation_size = std::mem::size_of::() * in_seq.size; - if out_seq.capacity < in_seq.size { - // SAFETY: The memory in out_seq.data is owned by C. - let data = unsafe { libc::realloc(out_seq.data as *mut _, allocation_size) }; - if data.is_null() { - return false; - } - out_seq.data = data as *mut _; - out_seq.capacity = in_seq.size; - } - // SAFETY: The memory areas don't overlap. - unsafe { - libc::memcpy( - out_seq.data as *mut _, - in_seq.data as *const _, - allocation_size, - ); - } - out_seq.size = in_seq.size; - true + // SAFETY: There are no special preconditions to the sequence_copy function. + unsafe { $copy_func(in_seq as *const _, out_seq as *mut _) } } } }; diff --git a/rosidl_runtime_rs/src/string.rs b/rosidl_runtime_rs/src/string.rs index 305062a..201889a 100644 --- a/rosidl_runtime_rs/src/string.rs +++ b/rosidl_runtime_rs/src/string.rs @@ -29,9 +29,9 @@ use crate::traits::SequenceAlloc; pub struct String { /// Dynamic memory in this type is allocated and deallocated by C, but this is a detail that is managed by /// the relevant functions and trait impls. - data: *mut libc::c_char, - size: libc::size_t, - capacity: libc::size_t, + data: *mut std::os::raw::c_char, + size: usize, + capacity: usize, } /// A zero-terminated string of 16-bit characters. @@ -50,9 +50,9 @@ pub struct String { /// ``` #[repr(C)] pub struct WString { - data: *mut libc::c_ushort, - size: libc::size_t, - capacity: libc::size_t, + data: *mut std::os::raw::c_ushort, + size: usize, + capacity: usize, } /// A zero-terminated string of 8-bit characters with a length limit. @@ -117,9 +117,13 @@ macro_rules! string_impl { extern "C" { fn $init(s: *mut $string) -> bool; fn $fini(s: *mut $string); - fn $assignn(s: *mut $string, value: *const $char_type, n: libc::size_t) -> bool; - fn $sequence_init(seq: *mut Sequence<$string>, size: libc::size_t) -> bool; + fn $assignn(s: *mut $string, value: *const $char_type, n: usize) -> bool; + fn $sequence_init(seq: *mut Sequence<$string>, size: usize) -> bool; fn $sequence_fini(seq: *mut Sequence<$string>); + fn $sequence_copy( + in_seq: *const Sequence<$string>, + out_seq: *mut Sequence<$string>, + ) -> bool; } impl Default for $string { @@ -226,7 +230,7 @@ macro_rules! string_impl { unsafe impl Sync for $string {} impl SequenceAlloc for $string { - fn sequence_init(seq: &mut Sequence, size: libc::size_t) -> bool { + fn sequence_init(seq: &mut Sequence, size: usize) -> bool { // SAFETY: There are no special preconditions to the sequence_init function. unsafe { $sequence_init(seq as *mut _, size) } } @@ -235,9 +239,8 @@ macro_rules! string_impl { unsafe { $sequence_fini(seq as *mut _) } } fn sequence_copy(in_seq: &Sequence, out_seq: &mut Sequence) -> bool { - out_seq.resize_to_at_least(in_seq.len()); - out_seq.clone_from_slice(in_seq.as_slice()); - true + // SAFETY: There are no special preconditions to the sequence_copy function. + unsafe { $sequence_copy(in_seq as *const _, out_seq as *mut _) } } } }; @@ -245,7 +248,7 @@ macro_rules! string_impl { string_impl!( String, - libc::c_char, + std::os::raw::c_char, u8, from_utf8_lossy, rosidl_runtime_c__String__init, @@ -257,7 +260,7 @@ string_impl!( ); string_impl!( WString, - libc::c_ushort, + std::os::raw::c_ushort, u16, from_utf16_lossy, rosidl_runtime_c__U16String__init, @@ -330,7 +333,7 @@ impl Debug for BoundedString { } impl Deref for BoundedString { - type Target = [libc::c_char]; + type Target = [std::os::raw::c_char]; fn deref(&self) -> &Self::Target { self.inner.deref() } @@ -349,7 +352,7 @@ impl Display for BoundedString { } impl SequenceAlloc for BoundedString { - fn sequence_init(seq: &mut Sequence, size: libc::size_t) -> bool { + fn sequence_init(seq: &mut Sequence, size: usize) -> bool { // SAFETY: There are no special preconditions to the rosidl_runtime_c__String__Sequence__init function. unsafe { rosidl_runtime_c__String__Sequence__init(seq as *mut Sequence as *mut _, size) @@ -415,7 +418,7 @@ impl Display for BoundedWString { } impl SequenceAlloc for BoundedWString { - fn sequence_init(seq: &mut Sequence, size: libc::size_t) -> bool { + fn sequence_init(seq: &mut Sequence, size: usize) -> bool { // SAFETY: There are no special preconditions to the rosidl_runtime_c__U16String__Sequence__init function. unsafe { rosidl_runtime_c__U16String__Sequence__init(seq as *mut Sequence as *mut _, size) diff --git a/rosidl_runtime_rs/src/traits.rs b/rosidl_runtime_rs/src/traits.rs index 4737bd1..d468a42 100644 --- a/rosidl_runtime_rs/src/traits.rs +++ b/rosidl_runtime_rs/src/traits.rs @@ -24,7 +24,7 @@ use std::fmt::Debug; /// User code never needs to call these trait methods, much less implement this trait. pub trait SequenceAlloc: Sized { /// Wraps the corresponding init function generated by `rosidl_generator_c`. - fn sequence_init(seq: &mut crate::Sequence, size: libc::size_t) -> bool; + fn sequence_init(seq: &mut crate::Sequence, size: usize) -> bool; /// Wraps the corresponding fini function generated by `rosidl_generator_c`. fn sequence_fini(seq: &mut crate::Sequence); /// Wraps the corresponding copy function generated by `rosidl_generator_c`. @@ -42,7 +42,7 @@ pub trait RmwMessage: Clone + Debug + Default + Send + Sync + Message { const TYPE_NAME: &'static str; /// Get a pointer to the correct `rosidl_message_type_support_t` structure. - fn get_type_support() -> libc::uintptr_t; + fn get_type_support() -> *const std::os::raw::c_void; } /// Trait for types that can be used in a `rclrs::Subscription` and a `rclrs::Publisher`. @@ -158,5 +158,5 @@ pub trait Service: 'static { type Response: Message; /// Get a pointer to the correct `rosidl_service_type_support_t` structure. - fn get_type_support() -> libc::uintptr_t; + fn get_type_support() -> *const std::os::raw::c_void; } From 9dad5365a86b3d4846a89b4102ebae1407d9b8e4 Mon Sep 17 00:00:00 2001 From: Sam Privett Date: Sun, 13 Nov 2022 15:30:52 -0800 Subject: [PATCH 25/40] Extend string types (#293) --- rosidl_runtime_rs/src/string.rs | 132 +++++++++++++++++++++++++++----- 1 file changed, 111 insertions(+), 21 deletions(-) diff --git a/rosidl_runtime_rs/src/string.rs b/rosidl_runtime_rs/src/string.rs index 201889a..44043a6 100644 --- a/rosidl_runtime_rs/src/string.rs +++ b/rosidl_runtime_rs/src/string.rs @@ -126,21 +126,6 @@ macro_rules! string_impl { ) -> bool; } - impl Default for $string { - fn default() -> Self { - let mut msg = Self { - data: std::ptr::null_mut(), - size: 0, - capacity: 0, - }; - // SAFETY: Passing in a zeroed string is safe. - if !unsafe { $init(&mut msg as *mut _) } { - panic!("Sinit failed"); - } - msg - } - } - impl Clone for $string { fn clone(&self) -> Self { let mut msg = Self::default(); @@ -158,6 +143,21 @@ macro_rules! string_impl { } } + impl Default for $string { + fn default() -> Self { + let mut msg = Self { + data: std::ptr::null_mut(), + size: 0, + capacity: 0, + }; + // SAFETY: Passing in a zeroed string is safe. + if !unsafe { $init(&mut msg as *mut _) } { + panic!("$init failed"); + } + msg + } + } + // It's not guaranteed that there are no interior null bytes, hence no Deref to CStr. // This does not include the null byte at the end! impl Deref for $string { @@ -200,15 +200,39 @@ macro_rules! string_impl { impl Eq for $string {} - impl Hash for $string { - fn hash(&self, state: &mut H) { - self.deref().hash(state) + impl Extend for $string { + fn extend>(&mut self, iter: I) { + let mut s = self.to_string(); + s.extend(iter); + *self = Self::from(s.as_str()); } } - impl PartialEq for $string { - fn eq(&self, other: &Self) -> bool { - self.deref().eq(other.deref()) + impl<'a> Extend<&'a char> for $string { + fn extend>(&mut self, iter: I) { + self.extend(iter.into_iter().cloned()); + } + } + + impl FromIterator for $string { + fn from_iter>(iter: I) -> Self { + let mut buf = <$string>::default(); + buf.extend(iter); + buf + } + } + + impl<'a> FromIterator<&'a char> for $string { + fn from_iter>(iter: I) -> Self { + let mut buf = <$string>::default(); + buf.extend(iter); + buf + } + } + + impl Hash for $string { + fn hash(&self, state: &mut H) { + self.deref().hash(state) } } @@ -218,6 +242,12 @@ macro_rules! string_impl { } } + impl PartialEq for $string { + fn eq(&self, other: &Self) -> bool { + self.deref().eq(other.deref()) + } + } + impl PartialOrd for $string { fn partial_cmp(&self, other: &Self) -> Option { self.deref().partial_cmp(other.deref()) @@ -503,4 +533,64 @@ mod tests { s.as_str().try_into().unwrap() } } + + #[test] + fn string_from_char_iterator() { + // Base char case + let expected = String::from("abc"); + let actual = "abc".chars().collect::(); + + assert_eq!(expected, actual); + + // Empty case + let expected = String::from(""); + let actual = "".chars().collect::(); + + assert_eq!(expected, actual); + + // Non-ascii char case + let expected = String::from("Grüß Gott! 𝕊"); + let actual = "Grüß Gott! 𝕊".chars().collect::(); + + assert_eq!(expected, actual); + } + + #[test] + fn extend_string_with_char_iterator() { + let expected = WString::from("abcdef"); + let mut actual = WString::from("abc"); + actual.extend("def".chars()); + + assert_eq!(expected, actual); + } + + #[test] + fn wstring_from_char_iterator() { + // Base char case + let expected = WString::from("abc"); + let actual = "abc".chars().collect::(); + + assert_eq!(expected, actual); + + // Empty case + let expected = WString::from(""); + let actual = "".chars().collect::(); + + assert_eq!(expected, actual); + + // Non-ascii char case + let expected = WString::from("Grüß Gott! 𝕊"); + let actual = "Grüß Gott! 𝕊".chars().collect::(); + + assert_eq!(expected, actual); + } + + #[test] + fn extend_wstring_with_char_iterator() { + let expected = WString::from("abcdef"); + let mut actual = WString::from("abc"); + actual.extend("def".chars()); + + assert_eq!(expected, actual); + } } From 63706fbf80dfc81d452662d1b1dcf8c4b9778ff2 Mon Sep 17 00:00:00 2001 From: Nikolai Morin Date: Sun, 12 Mar 2023 12:03:48 +0100 Subject: [PATCH 26/40] Improve efficiency of deserializing strings (#300) --- rosidl_runtime_rs/src/string/serde.rs | 93 +++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 4 deletions(-) diff --git a/rosidl_runtime_rs/src/string/serde.rs b/rosidl_runtime_rs/src/string/serde.rs index 2df52d5..be1058a 100644 --- a/rosidl_runtime_rs/src/string/serde.rs +++ b/rosidl_runtime_rs/src/string/serde.rs @@ -1,13 +1,98 @@ -use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt; -use super::{BoundedString, BoundedWString, String, WString}; +use serde::{ + de::{Error, SeqAccess, Visitor}, + Deserialize, Deserializer, Serialize, Serializer, +}; + +use super::{ + rosidl_runtime_c__String__assignn, rosidl_runtime_c__U16String__assignn, BoundedString, + BoundedWString, String, WString, +}; + +struct StringVisitor; +struct WStringVisitor; + +impl<'de> Visitor<'de> for StringVisitor { + type Value = String; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a string") + } + + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + Ok(String::from(v)) + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: Error, + { + let mut msg = String { + data: std::ptr::null_mut(), + size: 0, + capacity: 0, + }; + // SAFETY: This is doing the same thing as rosidl_runtime_c__String__copy. + unsafe { + rosidl_runtime_c__String__assignn(&mut msg, v.as_ptr() as *const _, v.len()); + } + Ok(msg) + } + + // We don't implement visit_bytes_buf, since the data in a string must always be managed by C. +} + +impl<'de> Visitor<'de> for WStringVisitor { + type Value = WString; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a string") + } + + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + Ok(WString::from(v)) + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut buf = if let Some(size) = seq.size_hint() { + Vec::with_capacity(size) + } else { + Vec::new() + }; + while let Some(el) = seq.next_element::()? { + buf.push(el); + } + let mut msg = WString { + data: std::ptr::null_mut(), + size: 0, + capacity: 0, + }; + // SAFETY: This is doing the same thing as rosidl_runtime_c__U16String__copy. + unsafe { + rosidl_runtime_c__U16String__assignn(&mut msg, buf.as_ptr(), buf.len()); + } + Ok(msg) + } + + // We don't implement visit_bytes_buf, since the data in a string must always be managed by C. +} impl<'de> Deserialize<'de> for String { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { - std::string::String::deserialize(deserializer).map(|s| Self::from(s.as_str())) + deserializer.deserialize_string(StringVisitor) } } @@ -29,7 +114,7 @@ impl<'de> Deserialize<'de> for WString { where D: Deserializer<'de>, { - std::string::String::deserialize(deserializer).map(|s| Self::from(s.as_str())) + deserializer.deserialize_string(WStringVisitor) } } From 20127cfb5f56da1a657cb553ac0bebce6d3b877a Mon Sep 17 00:00:00 2001 From: Sam Privett Date: Mon, 24 Jul 2023 08:05:42 -0700 Subject: [PATCH 27/40] - Upgraded bindgen to 0.66.1 (#323) - Upgraded libloading to 0.8 - Added instructions to rerun build scripts if dependent files have changed - Enabled bindgen to invalidate the built crate whenever any transitively included header files from the wrapper change - Removed some default true options from our bindgen builder Co-authored-by: Sam Privett --- rosidl_runtime_rs/build.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rosidl_runtime_rs/build.rs b/rosidl_runtime_rs/build.rs index fbc3c85..b895cd2 100644 --- a/rosidl_runtime_rs/build.rs +++ b/rosidl_runtime_rs/build.rs @@ -20,4 +20,7 @@ fn main() { let library_path = Path::new(ament_prefix_path).join("lib"); println!("cargo:rustc-link-search=native={}", library_path.display()); } + + // Invalidate the built crate whenever this script changes + println!("cargo:rerun-if-changed=build.rs"); } From 6550ea0349c356a9549c34dcd061f6a0a2051250 Mon Sep 17 00:00:00 2001 From: Esteve Fernandez <33620+esteve@users.noreply.github.com> Date: Tue, 7 Nov 2023 16:38:55 +0100 Subject: [PATCH 28/40] Version 0.4.0 (#343) Signed-off-by: Esteve Fernandez --- rosidl_runtime_rs/Cargo.toml | 2 +- rosidl_runtime_rs/package.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rosidl_runtime_rs/Cargo.toml b/rosidl_runtime_rs/Cargo.toml index e15ef0f..2a363be 100644 --- a/rosidl_runtime_rs/Cargo.toml +++ b/rosidl_runtime_rs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rosidl_runtime_rs" -version = "0.3.1" +version = "0.4.0" # This project is not military-sponsored, Jacob's employment contract just requires him to use this email address authors = ["Esteve Fernandez ", "Nikolai Morin ", "Jacob Hassold "] edition = "2021" diff --git a/rosidl_runtime_rs/package.xml b/rosidl_runtime_rs/package.xml index 71bb5db..5e97ba2 100644 --- a/rosidl_runtime_rs/package.xml +++ b/rosidl_runtime_rs/package.xml @@ -4,7 +4,7 @@ schematypens="http://www.w3.org/2001/XMLSchema"?> rosidl_runtime_rs - 0.3.1 + 0.4.0 Message generation code shared by Rust projects in ROS 2 Jacob Hassold From f4e79de6bd3a7c538ae255dbd7717970a1756081 Mon Sep 17 00:00:00 2001 From: Esteve Fernandez <33620+esteve@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:11:56 +0100 Subject: [PATCH 29/40] Revert "Version 0.4.0 (#343)" (#344) This reverts commit 8948409e0c6d9ced291b5cffda82036d067bd36d. --- rosidl_runtime_rs/Cargo.toml | 2 +- rosidl_runtime_rs/package.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rosidl_runtime_rs/Cargo.toml b/rosidl_runtime_rs/Cargo.toml index 2a363be..e15ef0f 100644 --- a/rosidl_runtime_rs/Cargo.toml +++ b/rosidl_runtime_rs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rosidl_runtime_rs" -version = "0.4.0" +version = "0.3.1" # This project is not military-sponsored, Jacob's employment contract just requires him to use this email address authors = ["Esteve Fernandez ", "Nikolai Morin ", "Jacob Hassold "] edition = "2021" diff --git a/rosidl_runtime_rs/package.xml b/rosidl_runtime_rs/package.xml index 5e97ba2..71bb5db 100644 --- a/rosidl_runtime_rs/package.xml +++ b/rosidl_runtime_rs/package.xml @@ -4,7 +4,7 @@ schematypens="http://www.w3.org/2001/XMLSchema"?> rosidl_runtime_rs - 0.4.0 + 0.3.1 Message generation code shared by Rust projects in ROS 2 Jacob Hassold From e5fc50e1edfc5c6c2cea4a7f2fbd4bb521f80742 Mon Sep 17 00:00:00 2001 From: Esteve Fernandez <33620+esteve@users.noreply.github.com> Date: Tue, 7 Nov 2023 18:19:40 +0100 Subject: [PATCH 30/40] Version 0.4.0 (#346) --- rosidl_runtime_rs/Cargo.toml | 2 +- rosidl_runtime_rs/package.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rosidl_runtime_rs/Cargo.toml b/rosidl_runtime_rs/Cargo.toml index e15ef0f..2a363be 100644 --- a/rosidl_runtime_rs/Cargo.toml +++ b/rosidl_runtime_rs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rosidl_runtime_rs" -version = "0.3.1" +version = "0.4.0" # This project is not military-sponsored, Jacob's employment contract just requires him to use this email address authors = ["Esteve Fernandez ", "Nikolai Morin ", "Jacob Hassold "] edition = "2021" diff --git a/rosidl_runtime_rs/package.xml b/rosidl_runtime_rs/package.xml index 71bb5db..5e97ba2 100644 --- a/rosidl_runtime_rs/package.xml +++ b/rosidl_runtime_rs/package.xml @@ -4,7 +4,7 @@ schematypens="http://www.w3.org/2001/XMLSchema"?> rosidl_runtime_rs - 0.3.1 + 0.4.0 Message generation code shared by Rust projects in ROS 2 Jacob Hassold From 0570fc0f54e198acbaa8ecd0101ec00589320f07 Mon Sep 17 00:00:00 2001 From: Esteve Fernandez <33620+esteve@users.noreply.github.com> Date: Fri, 24 Nov 2023 16:13:15 +0100 Subject: [PATCH 31/40] Allow rustdoc to run without a ROS distribution (#347) * Allow rustdoc to run without a ROS distribution * Extensions to get working * Fail if generate_docs feature is not enabled and ROS_DISTRO is not set * Fix formatting * Fix formatting * Make clippy slightly less unhappy * Do not run clippy for all features if checking rclrs * Clean up rcl_bindings.rs * Fix comparison * Avoid warnings for rosidl_runtime_rs * Avoid running cargo test with all features for rclrs * Ignore rosidl_runtime_rs for cargo test * rosidl_runtime_rs does not have a dyn_msg feature * Add comment about the generate_docs feature --------- Co-authored-by: carter --- rosidl_runtime_rs/Cargo.toml | 13 +++++++++++++ rosidl_runtime_rs/build.rs | 37 +++++++++++++++++++++--------------- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/rosidl_runtime_rs/Cargo.toml b/rosidl_runtime_rs/Cargo.toml index 2a363be..e679b87 100644 --- a/rosidl_runtime_rs/Cargo.toml +++ b/rosidl_runtime_rs/Cargo.toml @@ -17,8 +17,21 @@ path = "src/lib.rs" # formats such as JSON, YAML, Pickle, etc. serde = { version = "1", optional = true } +[features] +default = [] +# This feature is solely for the purpose of being able to generate documetation without a ROS installation +# The only intended usage of this feature is for docs.rs builders to work, and is not intended to be used by end users +generate_docs = [] + [dev-dependencies] # Needed for writing property tests quickcheck = "1" # Needed for testing serde support serde_json = "1" + +[build-dependencies] +# Needed for uploading documentation to docs.rs +cfg-if = "1.0.0" + +[package.metadata.docs.rs] +features = ["generate_docs"] diff --git a/rosidl_runtime_rs/build.rs b/rosidl_runtime_rs/build.rs index b895cd2..fb98aeb 100644 --- a/rosidl_runtime_rs/build.rs +++ b/rosidl_runtime_rs/build.rs @@ -1,24 +1,31 @@ -use std::env; -use std::path::Path; +cfg_if::cfg_if! { + if #[cfg(not(feature="generate_docs"))] { + use std::env; + use std::path::Path; -const AMENT_PREFIX_PATH: &str = "AMENT_PREFIX_PATH"; + const AMENT_PREFIX_PATH: &str = "AMENT_PREFIX_PATH"; -fn get_env_var_or_abort(env_var: &'static str) -> String { - if let Ok(value) = env::var(env_var) { - value - } else { - panic!( - "{} environment variable not set - please source ROS 2 installation first.", - env_var - ); + fn get_env_var_or_abort(env_var: &'static str) -> String { + if let Ok(value) = env::var(env_var) { + value + } else { + panic!( + "{} environment variable not set - please source ROS 2 installation first.", + env_var + ); + } + } } } fn main() { - let ament_prefix_path_list = get_env_var_or_abort(AMENT_PREFIX_PATH); - for ament_prefix_path in ament_prefix_path_list.split(':') { - let library_path = Path::new(ament_prefix_path).join("lib"); - println!("cargo:rustc-link-search=native={}", library_path.display()); + #[cfg(not(feature = "generate_docs"))] + { + let ament_prefix_path_list = get_env_var_or_abort(AMENT_PREFIX_PATH); + for ament_prefix_path in ament_prefix_path_list.split(':') { + let library_path = Path::new(ament_prefix_path).join("lib"); + println!("cargo:rustc-link-search=native={}", library_path.display()); + } } // Invalidate the built crate whenever this script changes From 3944ceafb08d27e79a6cb9738e0687f5df8a4866 Mon Sep 17 00:00:00 2001 From: Esteve Fernandez <33620+esteve@users.noreply.github.com> Date: Tue, 28 Nov 2023 16:48:36 +0100 Subject: [PATCH 32/40] Update GitHub actions (#352) * Update GitHub actions * Downgrade rust-toolchain to the latest stable version (1.74.0) from 1.80.0 * Fix issues with latest clippy * Fix rustdoc check issue * Update Rust toolchain in Dockerfile --- rosidl_runtime_rs/src/string.rs | 2 +- rosidl_runtime_rs/src/string/serde.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rosidl_runtime_rs/src/string.rs b/rosidl_runtime_rs/src/string.rs index 44043a6..14025fc 100644 --- a/rosidl_runtime_rs/src/string.rs +++ b/rosidl_runtime_rs/src/string.rs @@ -250,7 +250,7 @@ macro_rules! string_impl { impl PartialOrd for $string { fn partial_cmp(&self, other: &Self) -> Option { - self.deref().partial_cmp(other.deref()) + Some(self.cmp(other)) } } diff --git a/rosidl_runtime_rs/src/string/serde.rs b/rosidl_runtime_rs/src/string/serde.rs index be1058a..fe68661 100644 --- a/rosidl_runtime_rs/src/string/serde.rs +++ b/rosidl_runtime_rs/src/string/serde.rs @@ -125,7 +125,7 @@ impl Serialize for WString { { // Not particularly efficient // SAFETY: See the Display implementation. - let u16_slice = unsafe { std::slice::from_raw_parts(self.data as *mut u16, self.size) }; + let u16_slice = unsafe { std::slice::from_raw_parts(self.data, self.size) }; let s = std::string::String::from_utf16_lossy(u16_slice); serializer.serialize_str(&s) } From 85f23d6ce7cdffcd6b8a144d395e38e0bfe2bf1d Mon Sep 17 00:00:00 2001 From: Esteve Fernandez <33620+esteve@users.noreply.github.com> Date: Tue, 28 Nov 2023 16:49:19 +0100 Subject: [PATCH 33/40] Version 0.4.1 (#353) --- rosidl_runtime_rs/Cargo.toml | 2 +- rosidl_runtime_rs/package.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rosidl_runtime_rs/Cargo.toml b/rosidl_runtime_rs/Cargo.toml index e679b87..4e48916 100644 --- a/rosidl_runtime_rs/Cargo.toml +++ b/rosidl_runtime_rs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rosidl_runtime_rs" -version = "0.4.0" +version = "0.4.1" # This project is not military-sponsored, Jacob's employment contract just requires him to use this email address authors = ["Esteve Fernandez ", "Nikolai Morin ", "Jacob Hassold "] edition = "2021" diff --git a/rosidl_runtime_rs/package.xml b/rosidl_runtime_rs/package.xml index 5e97ba2..44e55f3 100644 --- a/rosidl_runtime_rs/package.xml +++ b/rosidl_runtime_rs/package.xml @@ -4,7 +4,7 @@ schematypens="http://www.w3.org/2001/XMLSchema"?> rosidl_runtime_rs - 0.4.0 + 0.4.1 Message generation code shared by Rust projects in ROS 2 Jacob Hassold From 60c99227a6811d9ae6700bef1834e3b456b4c3a3 Mon Sep 17 00:00:00 2001 From: Nathan Wiebe Neufeldt Date: Fri, 26 Apr 2024 00:13:22 -0400 Subject: [PATCH 34/40] Fix Unicode handling in String and WString (#362) According to https://design.ros2.org/articles/wide_strings.html, the `string` and `wstring` types must use the UTF-8 and UTF-16 encodings (not necessarily enforced), cannot contain null bytes or words (enforced), and, when bounded, are measured in terms of bytes or words. Moreover, though the rosidl_runtime_c `U16String` type uses `uint_least16_t`, Rust guarantees the existence of a precise `u16` type, so we should use that instead of `ushort`, which isn't guaranteed to be the same as `uint_least16_t` either. (Rust doesn't support platforms where `uint_least16_t != uint16_t`.) --- rosidl_runtime_rs/src/string.rs | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/rosidl_runtime_rs/src/string.rs b/rosidl_runtime_rs/src/string.rs index 14025fc..ba15851 100644 --- a/rosidl_runtime_rs/src/string.rs +++ b/rosidl_runtime_rs/src/string.rs @@ -10,7 +10,7 @@ mod serde; use crate::sequence::Sequence; use crate::traits::SequenceAlloc; -/// A zero-terminated string of 8-bit characters. +/// A zero-terminated UTF-8 string. /// /// The layout of this type is the same as `rosidl_runtime_c__String`. See the /// [`Message`](crate::Message) trait for background information on this topic. @@ -34,7 +34,7 @@ pub struct String { capacity: usize, } -/// A zero-terminated string of 16-bit characters. +/// A zero-terminated UTF-16 string. /// /// The layout of this type is the same as `rosidl_runtime_c__U16String`. See the /// [`Message`](crate::Message) trait for background information on this topic. @@ -50,15 +50,15 @@ pub struct String { /// ``` #[repr(C)] pub struct WString { - data: *mut std::os::raw::c_ushort, + data: *mut u16, size: usize, capacity: usize, } -/// A zero-terminated string of 8-bit characters with a length limit. +/// A zero-terminated UTF-8 string with a length limit. /// /// The same as [`String`], but it cannot be constructed from a string that is too large. -/// The length is measured as the number of Unicode scalar values, not bytes. +/// The length is measured as the number of bytes. /// /// # Example /// @@ -77,10 +77,10 @@ pub struct BoundedString { inner: String, } -/// A zero-terminated string of 16-bit characters with a length limit. +/// A zero-terminated UTF-16 string with a length limit. /// /// The same as [`WString`], but it cannot be constructed from a string that is too large. -/// The length is measured as the number of Unicode scalar values, not bytes. +/// The length is measured as the number of 16-bit words. /// /// # Example /// @@ -290,7 +290,7 @@ string_impl!( ); string_impl!( WString, - std::os::raw::c_ushort, + u16, u16, from_utf16_lossy, rosidl_runtime_c__U16String__init, @@ -406,7 +406,7 @@ impl SequenceAlloc for BoundedString { impl TryFrom<&str> for BoundedString { type Error = StringExceedsBoundsError; fn try_from(s: &str) -> Result { - let length = s.chars().count(); + let length = s.len(); if length <= N { Ok(Self { inner: String::from(s), @@ -472,14 +472,12 @@ impl SequenceAlloc for BoundedWString { impl TryFrom<&str> for BoundedWString { type Error = StringExceedsBoundsError; fn try_from(s: &str) -> Result { - let length = s.chars().count(); - if length <= N { - Ok(Self { - inner: WString::from(s), - }) + let inner = WString::from(s); + if inner.size <= N { + Ok(Self { inner }) } else { Err(StringExceedsBoundsError { - len: length, + len: inner.size, upper_bound: N, }) } From 4242802bac42f64dc86ac26214ac22b00a8ee726 Mon Sep 17 00:00:00 2001 From: Grey Date: Mon, 13 May 2024 21:18:01 +0800 Subject: [PATCH 35/40] Use nightly for style check (#396) * Use nightly for style check Signed-off-by: Michael X. Grey * Install nightly for cargo +nightly fmt Signed-off-by: Michael X. Grey * Fix style in examples Signed-off-by: Michael X. Grey * Update style for rosidl_runtime_rs Signed-off-by: Michael X. Grey * Add a comment indicating that nightly release is needed for formatting Signed-off-by: Michael X. Grey --------- Signed-off-by: Michael X. Grey --- rosidl_runtime_rs/src/sequence.rs | 12 +++++++----- rosidl_runtime_rs/src/string.rs | 15 ++++++++------- rosidl_runtime_rs/src/traits.rs | 3 +-- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/rosidl_runtime_rs/src/sequence.rs b/rosidl_runtime_rs/src/sequence.rs index 3ac2127..9cc7920 100644 --- a/rosidl_runtime_rs/src/sequence.rs +++ b/rosidl_runtime_rs/src/sequence.rs @@ -1,8 +1,10 @@ -use std::cmp::Ordering; -use std::fmt::{self, Debug, Display}; -use std::hash::{Hash, Hasher}; -use std::iter::{Extend, FromIterator, FusedIterator}; -use std::ops::{Deref, DerefMut}; +use std::{ + cmp::Ordering, + fmt::{self, Debug, Display}, + hash::{Hash, Hasher}, + iter::{Extend, FromIterator, FusedIterator}, + ops::{Deref, DerefMut}, +}; #[cfg(feature = "serde")] mod serde; diff --git a/rosidl_runtime_rs/src/string.rs b/rosidl_runtime_rs/src/string.rs index ba15851..74286b0 100644 --- a/rosidl_runtime_rs/src/string.rs +++ b/rosidl_runtime_rs/src/string.rs @@ -1,14 +1,15 @@ -use std::cmp::Ordering; -use std::ffi::CStr; -use std::fmt::{self, Debug, Display}; -use std::hash::{Hash, Hasher}; -use std::ops::{Deref, DerefMut}; +use std::{ + cmp::Ordering, + ffi::CStr, + fmt::{self, Debug, Display}, + hash::{Hash, Hasher}, + ops::{Deref, DerefMut}, +}; #[cfg(feature = "serde")] mod serde; -use crate::sequence::Sequence; -use crate::traits::SequenceAlloc; +use crate::{sequence::Sequence, traits::SequenceAlloc}; /// A zero-terminated UTF-8 string. /// diff --git a/rosidl_runtime_rs/src/traits.rs b/rosidl_runtime_rs/src/traits.rs index d468a42..15f2061 100644 --- a/rosidl_runtime_rs/src/traits.rs +++ b/rosidl_runtime_rs/src/traits.rs @@ -15,8 +15,7 @@ // DISTRIBUTION A. Approved for public release; distribution unlimited. // OPSEC #4584. // -use std::borrow::Cow; -use std::fmt::Debug; +use std::{borrow::Cow, fmt::Debug}; /// Internal trait that connects a particular `Sequence` instance to generated C functions /// that allocate and deallocate memory. From 38edbe9a8675d59ad509a3c9a5683f33254f360a Mon Sep 17 00:00:00 2001 From: Luca Della Vedova Date: Tue, 14 May 2024 20:08:41 +0800 Subject: [PATCH 36/40] Add parameter services (#342) * WIP Adding describe paramater service * Implement parameter setting services Signed-off-by: Luca Della Vedova * Restructure and cleanup Signed-off-by: Luca Della Vedova * Implement list_parameters with prefixes Signed-off-by: Luca Della Vedova * Minor cleanups Signed-off-by: Luca Della Vedova * Fix tests, cleanups Signed-off-by: Luca Della Vedova * Fix order of drop calls Signed-off-by: Luca Della Vedova * Add first bunch of unit tests for list and get / set parameters Signed-off-by: Luca Della Vedova * Clear warnings in rclrs Signed-off-by: Luca Della Vedova * Fix clippy, add set atomically tests Signed-off-by: Luca Della Vedova * Add describe parameter and get parameter types tests Signed-off-by: Luca Della Vedova * Minor cleanups, remove several unwraps Signed-off-by: Luca Della Vedova * Remove commented code Signed-off-by: Luca Della Vedova * Address first round of feedback Signed-off-by: Luca Della Vedova * Allow undeclared parameters in parameter getting services Signed-off-by: Luca Della Vedova * Clippy Signed-off-by: Luca Della Vedova * Run rustfmt Signed-off-by: Michael X. Grey * Update rclrs/src/parameter/service.rs Co-authored-by: jhdcs <48914066+jhdcs@users.noreply.github.com> * Change behavior to return NOT_SET for non existing parameters Signed-off-by: Luca Della Vedova * Make use_sim_time parameter read only Signed-off-by: Luca Della Vedova * Format Signed-off-by: Luca Della Vedova * Add a comment to denote why unwrap is safe Signed-off-by: Luca Della Vedova * Use main fmt Signed-off-by: Luca Della Vedova * Add a builder parameter to start parameter services Signed-off-by: Luca Della Vedova --------- Signed-off-by: Luca Della Vedova Signed-off-by: Michael X. Grey Co-authored-by: Michael X. Grey Co-authored-by: jhdcs <48914066+jhdcs@users.noreply.github.com> --- rosidl_runtime_rs/src/string.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/rosidl_runtime_rs/src/string.rs b/rosidl_runtime_rs/src/string.rs index 74286b0..9f0251c 100644 --- a/rosidl_runtime_rs/src/string.rs +++ b/rosidl_runtime_rs/src/string.rs @@ -1,4 +1,5 @@ use std::{ + borrow::Borrow, cmp::Ordering, ffi::CStr, fmt::{self, Debug, Display}, @@ -302,13 +303,17 @@ string_impl!( rosidl_runtime_c__U16String__Sequence__copy ); -impl From<&str> for String { - fn from(s: &str) -> Self { +impl From for String +where + T: Borrow, +{ + fn from(s: T) -> Self { let mut msg = Self { data: std::ptr::null_mut(), size: 0, capacity: 0, }; + let s = s.borrow(); // SAFETY: It's okay to pass a non-zero-terminated string here since assignn uses the // specified length and will append the 0 byte to the dest string itself. if !unsafe { From 8e9f2dea8ebc23254037f4d1220dabc05355cfd0 Mon Sep 17 00:00:00 2001 From: Oscarchoi Date: Sat, 22 Jun 2024 00:21:33 +0900 Subject: [PATCH 37/40] Fix panic on sequence_init() when size is 0 (#407) --- rosidl_runtime_rs/src/sequence.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rosidl_runtime_rs/src/sequence.rs b/rosidl_runtime_rs/src/sequence.rs index 9cc7920..22fbfba 100644 --- a/rosidl_runtime_rs/src/sequence.rs +++ b/rosidl_runtime_rs/src/sequence.rs @@ -510,8 +510,10 @@ macro_rules! impl_sequence_alloc_for_primitive_type { unsafe { // This allocates space and sets seq.size and seq.capacity to size let ret = $init_func(seq as *mut _, size); - // Zero memory, since it will be uninitialized if there is no default value - std::ptr::write_bytes(seq.data, 0u8, size); + if !seq.data.is_null() { + // Zero memory, since it will be uninitialized if there is no default value + std::ptr::write_bytes(seq.data, 0u8, size); + } ret } } From c7b6a0ff7d1e962547c4b04d0cd9c7a51890d53e Mon Sep 17 00:00:00 2001 From: Sam Privett Date: Sun, 29 Sep 2024 08:12:49 -0700 Subject: [PATCH 38/40] Add in missing nullptr check when calling `std::slice::from_raw_parts` (#416) * Add in missing nullptr check when calling `std::slice::from_raw_parts` * Added missing testcase --- rosidl_runtime_rs/src/sequence.rs | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/rosidl_runtime_rs/src/sequence.rs b/rosidl_runtime_rs/src/sequence.rs index 22fbfba..3d7cd58 100644 --- a/rosidl_runtime_rs/src/sequence.rs +++ b/rosidl_runtime_rs/src/sequence.rs @@ -261,18 +261,26 @@ where /// /// Equivalent to `&seq[..]`. pub fn as_slice(&self) -> &[T] { - // SAFETY: self.data points to self.size consecutive, initialized elements and - // isn't modified externally. - unsafe { std::slice::from_raw_parts(self.data, self.size) } + if self.data.is_null() { + &[] + } else { + // SAFETY: self.data is not null and points to self.size consecutive, + // initialized elements and isn't modified externally. + unsafe { std::slice::from_raw_parts(self.data, self.size) } + } } /// Extracts a mutable slice containing the entire sequence. /// /// Equivalent to `&mut seq[..]`. pub fn as_mut_slice(&mut self) -> &mut [T] { - // SAFETY: self.data points to self.size consecutive, initialized elements and - // isn't modified externally. - unsafe { std::slice::from_raw_parts_mut(self.data, self.size) } + if self.data.is_null() { + &mut [] + } else { + // SAFETY: self.data is not null and points to self.size consecutive, + // initialized elements and isn't modified externally. + unsafe { std::slice::from_raw_parts_mut(self.data, self.size) } + } } } @@ -666,6 +674,12 @@ mod tests { } } + #[test] + fn test_empty_sequence() { + assert!(Sequence::::default().is_empty()); + assert!(BoundedSequence::::default().is_empty()); + } + quickcheck! { fn test_extend(xs: Vec, ys: Vec) -> bool { let mut xs_seq = Sequence::new(xs.len()); From e22d669e5e4647243398ac4dab5a5575f8b53ab6 Mon Sep 17 00:00:00 2001 From: Nathan Wiebe Neufeldt Date: Sat, 26 Oct 2024 14:11:57 -0400 Subject: [PATCH 39/40] Action message support (#417) * Added action template * Added action generation * Added basic create_action_client function * dded action generation * checkin * Fix missing exported pre_field_serde field * Removed extra code * Sketch out action server construction and destruction This follows generally the same pattern as the service server. It required adding a typesupport function to the Action trait and pulling in some more rcl_action bindings. * Fix action typesupport function * Add ActionImpl trait with internal messages and services This is incomplete, since the service types aren't yet being generated. * Split srv.rs.em into idiomatic and rmw template files This results in the exact same file being produced for services, except for some whitespace changes. However, it enables actions to invoke the respective service template for its generation, similar to how the it works for services and their underlying messages. * Generate underlying service definitions for actions Not tested * Add runtime trait to get the UUID from a goal request C++ uses duck typing for this, knowing that for any `Action`, the type `Action::Impl::SendGoalService::Request` will always have a `goal_id` field of type `unique_identifier_msgs::msg::UUID` without having to prove this to the compiler. Rust's generics are more strict, requiring that this be proven using type bounds. The `Request` type is also action-specific as it contains a `goal` field containing the `Goal` message type of the action. We therefore cannot enforce that all `Request`s are a specific type in `rclrs`. This seems most easily represented using associated type bounds on the `SendGoalService` associated type within `ActionImpl`. To avoid introducing to `rosidl_runtime_rs` a circular dependency on message packages like `unique_identifier_msgs`, the `ExtractUuid` trait only operates on a byte array rather than a more nicely typed `UUID` message type. I'll likely revisit this as we introduce more similar bounds on the generated types. * Integrate RMW message methods into ActionImpl Rather than having a bunch of standalone traits implementing various message functions like `ExtractUuid` and `SetAccepted`, with the trait bounds on each associated type in `ActionImpl`, we'll instead add these functions directly to the `ActionImpl` trait. This is simpler on both the rosidl_runtime_rs and the rclrs side. * Add rosidl_runtime_rs::ActionImpl::create_feedback_message() Adds a trait method to create a feedback message given the goal ID and the user-facing feedback message type. Depending on how many times we do this, it may end up valuable to define a GoalUuid type in rosidl_runtime_rs itself. We wouldn't be able to utilize the `RCL_ACTION_UUID_SIZE` constant imported from `rcl_action`, but this is pretty much guaranteed to be 16 forever. Defining this method signature also required inverting the super-trait relationship between Action and ActionImpl. Now ActionImpl is the sub-trait as this gives it access to all of Action's associated types. Action doesn't need to care about anything from ActionImpl (hopefully). * Add GetResultService methods to ActionImpl * Implement ActionImpl trait methods in generator These still don't build without errors, but it's close. * Replace set_result_response_status with create_result_response rclrs needs to be able to generically construct result responses, including the user-defined result field. * Implement client-side trait methods for action messages This adds methods to ActionImpl for creating and accessing action-specific message types. These are needed by the rclrs ActionClient to generically read and write RMW messages. Due to issues with qualified paths in certain places (https://github.com/rust-lang/rust/issues/86935), the generator now refers directly to its service and message types rather than going through associated types of the various traits. This also makes the generated code a little easier to read, with the trait method signatures from rosidl_runtime_rs still enforcing type-safety. * Format the rosidl_runtime_rs::ActionImpl trait * Wrap longs lines in rosidl_generator_rs action.rs This at least makes the template easier to read, but also helps with the generated code. In general, the generated code could actually fit on one line for the function signatures, but it's not a big deal to split it across multiple. * Use idiomatic message types in Action trait This is user-facing and so should use the friendly message types. * Cleanup ActionImpl using type aliases Signed-off-by: Michael X. Grey * Formatting * Switch from std::os::raw::c_void to std::ffi::c_void While these are aliases of each other, we might as well use the more appropriate std::ffi version, as requested by reviewers. * Clean up rosidl_generator_rs's cmake files Some of the variables are present but no longer used. Others were not updated with the action changes. * Add a short doc page on the message generation pipeline This should help newcomers orient themselves around the rosidl_*_rs packages. --------- Signed-off-by: Michael X. Grey Co-authored-by: Esteve Fernandez Co-authored-by: Michael X. Grey --- rosidl_runtime_rs/src/lib.rs | 2 +- rosidl_runtime_rs/src/traits.rs | 96 ++++++++++++++++++++++++++++++++- 2 files changed, 95 insertions(+), 3 deletions(-) diff --git a/rosidl_runtime_rs/src/lib.rs b/rosidl_runtime_rs/src/lib.rs index 93f8441..7c5bad4 100644 --- a/rosidl_runtime_rs/src/lib.rs +++ b/rosidl_runtime_rs/src/lib.rs @@ -9,4 +9,4 @@ mod string; pub use string::{BoundedString, BoundedWString, String, StringExceedsBoundsError, WString}; mod traits; -pub use traits::{Message, RmwMessage, SequenceAlloc, Service}; +pub use traits::{Action, ActionImpl, Message, RmwMessage, SequenceAlloc, Service}; diff --git a/rosidl_runtime_rs/src/traits.rs b/rosidl_runtime_rs/src/traits.rs index 15f2061..1e09085 100644 --- a/rosidl_runtime_rs/src/traits.rs +++ b/rosidl_runtime_rs/src/traits.rs @@ -41,7 +41,7 @@ pub trait RmwMessage: Clone + Debug + Default + Send + Sync + Message { const TYPE_NAME: &'static str; /// Get a pointer to the correct `rosidl_message_type_support_t` structure. - fn get_type_support() -> *const std::os::raw::c_void; + fn get_type_support() -> *const std::ffi::c_void; } /// Trait for types that can be used in a `rclrs::Subscription` and a `rclrs::Publisher`. @@ -157,5 +157,97 @@ pub trait Service: 'static { type Response: Message; /// Get a pointer to the correct `rosidl_service_type_support_t` structure. - fn get_type_support() -> *const std::os::raw::c_void; + fn get_type_support() -> *const std::ffi::c_void; } + +/// Trait for actions. +/// +/// User code never needs to call this trait's method, much less implement this trait. +pub trait Action: 'static { + /// The goal message associated with this action. + type Goal: Message; + + /// The result message associated with this action. + type Result: Message; + + /// The feedback message associated with this action. + type Feedback: Message; + + /// Get a pointer to the correct `rosidl_action_type_support_t` structure. + fn get_type_support() -> *const std::ffi::c_void; +} + +/// Trait for action implementation details. +/// +/// User code never needs to implement this trait, nor use its associated types. +pub trait ActionImpl: 'static + Action { + /// The goal_status message associated with this action. + type GoalStatusMessage: Message; + + /// The feedback message associated with this action. + type FeedbackMessage: Message; + + /// The send_goal service associated with this action. + type SendGoalService: Service; + + /// The cancel_goal service associated with this action. + type CancelGoalService: Service; + + /// The get_result service associated with this action. + type GetResultService: Service; + + /// Create a goal request message with the given UUID and goal. + fn create_goal_request(goal_id: &[u8; 16], goal: RmwGoalData) -> RmwGoalRequest; + + /// Get the UUID of a goal request. + fn get_goal_request_uuid(request: &RmwGoalRequest) -> &[u8; 16]; + + /// Create a goal response message with the given acceptance and timestamp. + fn create_goal_response(accepted: bool, stamp: (i32, u32)) -> RmwGoalResponse; + + /// Get the `accepted` field of a goal response. + fn get_goal_response_accepted(response: &RmwGoalResponse) -> bool; + + /// Get the `stamp` field of a goal response. + fn get_goal_response_stamp(response: &RmwGoalResponse) -> (i32, u32); + + /// Create a feedback message with the given goal ID and contents. + fn create_feedback_message( + goal_id: &[u8; 16], + feedback: RmwFeedbackData, + ) -> RmwFeedbackMessage; + + /// Get the UUID of a feedback message. + fn get_feedback_message_uuid(feedback: &RmwFeedbackMessage) -> &[u8; 16]; + + /// Get the feedback of a feedback message. + fn get_feedback_message_feedback(feedback: &RmwFeedbackMessage) + -> &RmwFeedbackData; + + /// Create a result request message with the given goal ID. + fn create_result_request(goal_id: &[u8; 16]) -> RmwResultRequest; + + /// Get the UUID of a result request. + fn get_result_request_uuid(request: &RmwResultRequest) -> &[u8; 16]; + + /// Create a result response message with the given status and contents. + fn create_result_response(status: i8, result: RmwResultData) -> RmwResultResponse; + + /// Get the result of a result response. + fn get_result_response_result(response: &RmwResultResponse) -> &RmwResultData; + + /// Get the status of a result response. + fn get_result_response_status(response: &RmwResultResponse) -> i8; +} + +// Type definitions to simplify the ActionImpl trait +pub type RmwServiceRequest = <::Request as Message>::RmwMsg; +pub type RmwServiceResponse = <::Response as Message>::RmwMsg; +pub type RmwGoalRequest = RmwServiceRequest<::SendGoalService>; +pub type RmwGoalResponse = RmwServiceResponse<::SendGoalService>; +pub type RmwGoalData = <::Goal as Message>::RmwMsg; +pub type RmwFeedbackData = <::Feedback as Message>::RmwMsg; +pub type RmwFeedbackMessage = <::FeedbackMessage as Message>::RmwMsg; +pub type RmwResultRequest = RmwServiceRequest<::GetResultService>; +pub type RmwResultResponse = RmwServiceResponse<::GetResultService>; +pub type RmwResultData = <::Result as Message>::RmwMsg; From 499347698e524a9aaf226aa1c6470e2958a3f6e9 Mon Sep 17 00:00:00 2001 From: Grey Date: Mon, 2 Dec 2024 21:42:36 +0800 Subject: [PATCH 40/40] Update for clippy 1.83 (#441) * Update for clippy 1.83 Signed-off-by: Michael X. Grey * Update clippy for cfg(test) as well Signed-off-by: Michael X. Grey * Exclude dependencies from clippy test Signed-off-by: Michael X. Grey * Fix clippy for rosidl_runtime_rs Signed-off-by: Michael X. Grey --------- Signed-off-by: Michael X. Grey --- rosidl_runtime_rs/src/string/serde.rs | 2 +- rosidl_runtime_rs/src/traits.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/rosidl_runtime_rs/src/string/serde.rs b/rosidl_runtime_rs/src/string/serde.rs index fe68661..0ded3fd 100644 --- a/rosidl_runtime_rs/src/string/serde.rs +++ b/rosidl_runtime_rs/src/string/serde.rs @@ -13,7 +13,7 @@ use super::{ struct StringVisitor; struct WStringVisitor; -impl<'de> Visitor<'de> for StringVisitor { +impl Visitor<'_> for StringVisitor { type Value = String; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { diff --git a/rosidl_runtime_rs/src/traits.rs b/rosidl_runtime_rs/src/traits.rs index 1e09085..61d8c23 100644 --- a/rosidl_runtime_rs/src/traits.rs +++ b/rosidl_runtime_rs/src/traits.rs @@ -124,10 +124,11 @@ pub trait RmwMessage: Clone + Debug + Default + Send + Sync + Message { /// /// Memory ownership by C is achieved by calling `init()` when any string or sequence is created, /// as well as in the `Default` impl for messages. +/// /// User code can still create messages explicitly, which will not call `init()`, but this is not a -/// problem, since nothing is allocated this way. +/// problem, since nothing is allocated this way. +/// /// The `Drop` impl for any sequence or string will call `fini()`. - pub trait Message: Clone + Debug + Default + 'static + Send + Sync { /// The corresponding RMW-native message type. type RmwMsg: RmwMessage;