From 3094ff8af60361f4e19b375080aaa4a38f5e0b0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balthasar=20Sch=C3=BCss?= Date: Wed, 20 Aug 2025 01:00:01 +0200 Subject: [PATCH 01/25] Structured parameters initial draft --- Cargo.toml | 1 + Dockerfile | 4 ++ rclrs/Cargo.toml | 2 + rclrs/package.xml | 5 +- rclrs/src/lib.rs | 2 + rclrs/src/parameter.rs | 94 +++++++++++++++++++++++++++++++++++ rclrs_proc_macros/Cargo.toml | 18 +++++++ rclrs_proc_macros/src/impl.rs | 67 +++++++++++++++++++++++++ rclrs_proc_macros/src/lib.rs | 11 ++++ 9 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 rclrs_proc_macros/Cargo.toml create mode 100644 rclrs_proc_macros/src/impl.rs create mode 100644 rclrs_proc_macros/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index afac85b2a..0fb0e9a99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ "rclrs", + "rclrs_proc_macros", ] resolver = "2" diff --git a/Dockerfile b/Dockerfile index 07cb6970d..0d500fcd9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,6 +9,8 @@ RUN apt-get update && apt-get install -y \ libclang-dev \ tmux \ python3-pip \ + ros-humble-test-msgs \ + ros-humble-example-interfaces \ && rm -rf /var/lib/apt/lists/* # Install Rust @@ -22,8 +24,10 @@ COPY src/ros2_rust/docker/rosidl_rust_setup.sh / RUN ./rosidl_rust_setup.sh RUN mkdir -p /workspace && echo "Did you forget to mount the repository into the Docker container?" > /workspace/HELLO.txt +RUN echo -e "\nsource /opt/ros/${ROS_DISTRO}/setup.sh" WORKDIR /workspace + COPY src/ros2_rust/docker/rosidl_rust_entrypoint.sh / ENTRYPOINT ["/rosidl_rust_entrypoint.sh"] CMD ["/bin/bash"] diff --git a/rclrs/Cargo.toml b/rclrs/Cargo.toml index ee4d948cd..262ad79f0 100644 --- a/rclrs/Cargo.toml +++ b/rclrs/Cargo.toml @@ -36,6 +36,8 @@ rosidl_runtime_rs = "0.4" serde = { version = "1", optional = true, features = ["derive"] } serde-big-array = { version = "0.5.1", optional = true } +rclrs_proc_macros = {path = "../rclrs_proc_macros"} + [dev-dependencies] # Needed for e.g. writing yaml files in tests tempfile = "3.3.0" diff --git a/rclrs/package.xml b/rclrs/package.xml index d947bbf8f..8d91b2de8 100644 --- a/rclrs/package.xml +++ b/rclrs/package.xml @@ -19,9 +19,10 @@ builtin_interfaces rcl_interfaces rosgraph_msgs + rosidl_default_generators - test_msgs - example_interfaces + test_msgs + example_interfaces ament_cargo diff --git a/rclrs/src/lib.rs b/rclrs/src/lib.rs index 3952682a1..f3c4f9155 100644 --- a/rclrs/src/lib.rs +++ b/rclrs/src/lib.rs @@ -223,3 +223,5 @@ pub use time::*; use time_source::*; pub use wait_set::*; pub use worker::*; + +pub use rclrs_proc_macros::StructuredParameters; diff --git a/rclrs/src/parameter.rs b/rclrs/src/parameter.rs index fe9fa0918..699a0d413 100644 --- a/rclrs/src/parameter.rs +++ b/rclrs/src/parameter.rs @@ -871,6 +871,38 @@ impl ParameterInterface { } } +pub trait StructuredParameters: Sized { + fn declare_structured( + node: &crate::NodeState, + name: &str, + ) -> core::result::Result; +} + +impl StructuredParameters for crate::MandatoryParameter { + fn declare_structured( + node: &crate::NodeState, + name: &str, + ) -> core::result::Result, crate::DeclarationError> { + node.declare_parameter(name).mandatory() + } +} +impl StructuredParameters for crate::OptionalParameter { + fn declare_structured( + node: &crate::NodeState, + name: &str, + ) -> core::result::Result, crate::DeclarationError> { + node.declare_parameter(name).optional() + } +} +impl StructuredParameters for crate::ReadOnlyParameter { + fn declare_structured( + node: &crate::NodeState, + name: &str, + ) -> core::result::Result, crate::DeclarationError> { + node.declare_parameter(name).read_only() + } +} + #[cfg(test)] mod tests { use super::*; @@ -1410,4 +1442,66 @@ mod tests { .optional() .unwrap(); } + + use crate as rclrs; + use rclrs_proc_macros::StructuredParameters; + + #[derive(StructuredParameters, Debug)] + struct SimpleStructuredParameters { + _mandatory: MandatoryParameter, + _optional: OptionalParameter, + _readonly: ReadOnlyParameter, + } + + #[test] + fn test_simple_structured_parameters() { + let args: Vec = [ + "test", + "--ros-args", + "-p", + "_mandatory:=1.0", + "-p", + "_optional:=1.0", + "-p", + "_readonly:=1.0", + ] + .into_iter() + .map(str::to_string) + .collect(); + + let context = crate::Context::new(args, InitOptions::default()).unwrap(); + let exec = context.create_basic_executor(); + let node = exec.create_node(NodeOptions::new("test")).unwrap(); + let _params = SimpleStructuredParameters::declare_structured(&node, "").unwrap(); + } + + #[derive(StructuredParameters, Debug)] + struct NestedStructuredParameters { + _simple: SimpleStructuredParameters, + _mandatory: MandatoryParameter>, + } + + #[test] + fn test_nested_structured_parameters() { + let args: Vec = [ + "test", + "--ros-args", + "-p", + "nested._simple._mandatory:=1.0", + "-p", + "nested._simple._optional:=1.0", + "-p", + "nested._simple._readonly:=1.0", + "-p", + "nested._mandatory:=foo", + ] + .into_iter() + .map(str::to_string) + .collect(); + + let context = crate::Context::new(args, InitOptions::default()).unwrap(); + let exec = context.create_basic_executor(); + let node = exec.create_node(NodeOptions::new("test")).unwrap(); + let _params = NestedStructuredParameters::declare_structured(&node, "nested").unwrap(); + } } diff --git a/rclrs_proc_macros/Cargo.toml b/rclrs_proc_macros/Cargo.toml new file mode 100644 index 000000000..51ee68bac --- /dev/null +++ b/rclrs_proc_macros/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name= "rclrs_proc_macros" +version = "0.0.1" +authors = ["Balthasar Schüss "] +edition = "2021" +license = "Apache-2.0" +description = "A rust library providing proc macros for rclrs" +rust-version = "1.75" + + +[lib] +path = "src/lib.rs" +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = "2.0" diff --git a/rclrs_proc_macros/src/impl.rs b/rclrs_proc_macros/src/impl.rs new file mode 100644 index 000000000..7607669b5 --- /dev/null +++ b/rclrs_proc_macros/src/impl.rs @@ -0,0 +1,67 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::DeriveInput; + +pub(crate) fn derive_struct_parameters(input: DeriveInput) -> syn::Result { + let ident = input.ident; + + let fields = match input.data { + syn::Data::Struct(ref s) => &s.fields, + _ => { + return syn::Result::Err(syn::Error::new_spanned( + ident, + "StrucutredParameter trait can only be derived for structs", + )); + } + }; + + let field_types: Vec<_> = fields.iter().map(|f| &f.ty).collect(); + + let mut args = Vec::new(); + for f in fields { + let ident = f.ident.as_ref().unwrap(); + let ident_str = syn::LitStr::new(&f.ident.as_ref().unwrap().to_string(), ident.span()); + let field_type = match &f.ty { + syn::Type::Path(p) => { + let mut p = p.path.clone(); + for segment in &mut p.segments { + segment.arguments = syn::PathArguments::None; + } + p + } + e => { + return syn::Result::Err(syn::Error::new_spanned( + e, + "attribute can only be path type", + )); + } + }; + let r = quote! { + #ident : #field_type::declare_structured( + node, &{match name { + "" => #ident_str.to_string(), + prefix => [prefix, ".", #ident_str].concat(), + } + })?, + }; + args.push(r); + } + + let result = quote!( + impl #ident { + const _ASSERT_PARAMETER: fn() = || { + fn assert_parameter() {} + #( + assert_parameter::<#field_types>(); + )* + }; + } + + impl rclrs::StructuredParameters for #ident { + fn declare_structured(node: &rclrs::NodeState, name: &str) -> core::result::Result { + core::result::Result::Ok(Self{ #(#args)*}) + } + } + ); + syn::Result::Ok(result) +} diff --git a/rclrs_proc_macros/src/lib.rs b/rclrs_proc_macros/src/lib.rs new file mode 100644 index 000000000..fcac0543a --- /dev/null +++ b/rclrs_proc_macros/src/lib.rs @@ -0,0 +1,11 @@ +mod r#impl; +use proc_macro::TokenStream; +use syn::{parse_macro_input, DeriveInput}; + +#[proc_macro_derive(StructuredParameters)] +pub fn derive_struct_parameters(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + r#impl::derive_struct_parameters(input) + .unwrap_or_else(|e| e.to_compile_error()) + .into() +} From c76651c4a93f4f256652971b36a63377a8f96be9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balthasar=20Sch=C3=BCss?= Date: Thu, 28 Aug 2025 22:51:14 +0200 Subject: [PATCH 02/25] Default values for parameters --- rclrs/src/lib.rs | 1 + rclrs/src/parameter.rs | 96 +---------------- rclrs/src/parameter/structured.rs | 173 ++++++++++++++++++++++++++++++ rclrs_proc_macros/src/impl.rs | 53 ++++++--- rclrs_proc_macros/src/lib.rs | 2 +- 5 files changed, 218 insertions(+), 107 deletions(-) create mode 100644 rclrs/src/parameter/structured.rs diff --git a/rclrs/src/lib.rs b/rclrs/src/lib.rs index f3c4f9155..e19ad2915 100644 --- a/rclrs/src/lib.rs +++ b/rclrs/src/lib.rs @@ -213,6 +213,7 @@ pub use error::*; pub use executor::*; pub use logging::*; pub use node::*; +pub use parameter::structured::{ParameterOptions, StructuredParameters, StructuredParametersMeta}; pub use parameter::*; pub use publisher::*; pub use qos::*; diff --git a/rclrs/src/parameter.rs b/rclrs/src/parameter.rs index 699a0d413..73a54ecad 100644 --- a/rclrs/src/parameter.rs +++ b/rclrs/src/parameter.rs @@ -1,6 +1,7 @@ mod override_map; mod range; mod service; +pub mod structured; mod value; pub(crate) use override_map::*; @@ -13,6 +14,7 @@ use crate::vendor::rcl_interfaces::msg::rmw::{ParameterType, ParameterValue as R use crate::{ call_string_getter_with_rcl_node, rcl_bindings::*, Node, RclrsError, ENTITY_LIFECYCLE_MUTEX, }; +use std::default; use std::{ collections::{btree_map::Entry, BTreeMap}, fmt::Debug, @@ -871,38 +873,6 @@ impl ParameterInterface { } } -pub trait StructuredParameters: Sized { - fn declare_structured( - node: &crate::NodeState, - name: &str, - ) -> core::result::Result; -} - -impl StructuredParameters for crate::MandatoryParameter { - fn declare_structured( - node: &crate::NodeState, - name: &str, - ) -> core::result::Result, crate::DeclarationError> { - node.declare_parameter(name).mandatory() - } -} -impl StructuredParameters for crate::OptionalParameter { - fn declare_structured( - node: &crate::NodeState, - name: &str, - ) -> core::result::Result, crate::DeclarationError> { - node.declare_parameter(name).optional() - } -} -impl StructuredParameters for crate::ReadOnlyParameter { - fn declare_structured( - node: &crate::NodeState, - name: &str, - ) -> core::result::Result, crate::DeclarationError> { - node.declare_parameter(name).read_only() - } -} - #[cfg(test)] mod tests { use super::*; @@ -1442,66 +1412,4 @@ mod tests { .optional() .unwrap(); } - - use crate as rclrs; - use rclrs_proc_macros::StructuredParameters; - - #[derive(StructuredParameters, Debug)] - struct SimpleStructuredParameters { - _mandatory: MandatoryParameter, - _optional: OptionalParameter, - _readonly: ReadOnlyParameter, - } - - #[test] - fn test_simple_structured_parameters() { - let args: Vec = [ - "test", - "--ros-args", - "-p", - "_mandatory:=1.0", - "-p", - "_optional:=1.0", - "-p", - "_readonly:=1.0", - ] - .into_iter() - .map(str::to_string) - .collect(); - - let context = crate::Context::new(args, InitOptions::default()).unwrap(); - let exec = context.create_basic_executor(); - let node = exec.create_node(NodeOptions::new("test")).unwrap(); - let _params = SimpleStructuredParameters::declare_structured(&node, "").unwrap(); - } - - #[derive(StructuredParameters, Debug)] - struct NestedStructuredParameters { - _simple: SimpleStructuredParameters, - _mandatory: MandatoryParameter>, - } - - #[test] - fn test_nested_structured_parameters() { - let args: Vec = [ - "test", - "--ros-args", - "-p", - "nested._simple._mandatory:=1.0", - "-p", - "nested._simple._optional:=1.0", - "-p", - "nested._simple._readonly:=1.0", - "-p", - "nested._mandatory:=foo", - ] - .into_iter() - .map(str::to_string) - .collect(); - - let context = crate::Context::new(args, InitOptions::default()).unwrap(); - let exec = context.create_basic_executor(); - let node = exec.create_node(NodeOptions::new("test")).unwrap(); - let _params = NestedStructuredParameters::declare_structured(&node, "nested").unwrap(); - } } diff --git a/rclrs/src/parameter/structured.rs b/rclrs/src/parameter/structured.rs new file mode 100644 index 000000000..c538d5999 --- /dev/null +++ b/rclrs/src/parameter/structured.rs @@ -0,0 +1,173 @@ +use crate::NodeState; + +pub enum DefaultForbidden {} + +pub struct ParameterOptions<'a, 'b, T> { + node: &'a crate::NodeState, + name: &'b str, + default: Option, +} +pub trait StructuredParametersMeta: Sized { + fn declare_structured_( + options: ParameterOptions, + ) -> core::result::Result; +} + +pub trait StructuredParameters: Sized { + fn declare_structured( + options: ParameterOptions, + ) -> core::result::Result + where + Self: StructuredParametersMeta, + { + Self::declare_structured_(options) + } +} +impl StructuredParameters for crate::MandatoryParameter {} +impl StructuredParametersMeta for crate::MandatoryParameter { + fn declare_structured_( + options: ParameterOptions, + ) -> core::result::Result { + let builder = options.node.declare_parameter(options.name); + let builder = match options.default { + Some(default) => builder.default(default), + None => builder, + }; + builder.mandatory() + } +} +impl StructuredParameters for crate::ReadOnlyParameter {} +impl StructuredParametersMeta for crate::ReadOnlyParameter { + fn declare_structured_( + options: ParameterOptions, + ) -> core::result::Result { + let builder = options.node.declare_parameter(options.name); + let builder = match options.default { + Some(default) => builder.default(default), + None => builder, + }; + builder.read_only() + } +} +impl StructuredParameters for crate::OptionalParameter {} +impl StructuredParametersMeta for crate::OptionalParameter { + fn declare_structured_( + options: ParameterOptions, + ) -> core::result::Result { + let builder = options.node.declare_parameter(options.name); + let builder = match options.default { + Some(default) => builder.default(default), + None => builder, + }; + builder.optional() + } +} + +impl NodeState { + fn declare_parameters( + &self, + name: &str, + ) -> core::result::Result + where + T: StructuredParameters + StructuredParametersMeta, + { + T::declare_structured(ParameterOptions { + node: self, + name: name, + default: None, + }) + } +} + +mod tests { + use std::sync::Arc; + + use crate as rclrs; + use rclrs::parameter::structured::*; + use rclrs::CreateBasicExecutor; + use rclrs_proc_macros::StructuredParameters; + + #[derive(StructuredParameters, Debug)] + struct SimpleStructuredParameters { + _mandatory: rclrs::MandatoryParameter, + _optional: rclrs::OptionalParameter, + _readonly: rclrs::ReadOnlyParameter, + } + + #[test] + fn test_simple_structured_parameters() { + let args: Vec = [ + "test", + "--ros-args", + "-p", + "_mandatory:=1.0", + "-p", + "_optional:=1.0", + "-p", + "_readonly:=1.0", + ] + .into_iter() + .map(str::to_string) + .collect(); + + let context = crate::Context::new(args, rclrs::InitOptions::default()).unwrap(); + let exec = context.create_basic_executor(); + let node = exec.create_node(rclrs::NodeOptions::new("test")).unwrap(); + let _params: SimpleStructuredParameters = node.declare_parameters("").unwrap(); + println!("{:?}", _params); + } + + #[derive(StructuredParameters, Debug)] + struct NestedStructuredParameters { + _simple: SimpleStructuredParameters, + _mandatory: rclrs::MandatoryParameter>, + } + + #[test] + fn test_nested_structured_parameters() { + let args: Vec = [ + "test", + "--ros-args", + "-p", + "nested._simple._mandatory:=1.0", + "-p", + "nested._simple._optional:=1.0", + "-p", + "nested._simple._readonly:=1.0", + "-p", + "nested._mandatory:=foo", + ] + .into_iter() + .map(str::to_string) + .collect(); + + let context = crate::Context::new(args, rclrs::InitOptions::default()).unwrap(); + let exec = context.create_basic_executor(); + let node = exec.create_node(rclrs::NodeOptions::new("test")).unwrap(); + let _params: NestedStructuredParameters = node.declare_parameters("nested").unwrap(); + println!("{:?}", _params); + } + + #[derive(Debug, StructuredParameters)] + struct SimpleStructuredParametersWithDefaults { + #[param(default = 42.0)] + _mandatory: rclrs::MandatoryParameter, + #[param(default = 42.0)] + _optional: rclrs::OptionalParameter, + #[param(default = Arc::from("test"))] + _readonly: rclrs::ReadOnlyParameter>, + } + + #[test] + fn test_simple_structured_parameters_with_defaults() { + let args: Vec = ["test", "--ros-args"] + .into_iter() + .map(str::to_string) + .collect(); + let context = crate::Context::new(args, rclrs::InitOptions::default()).unwrap(); + let exec = context.create_basic_executor(); + let node = exec.create_node(rclrs::NodeOptions::new("test")).unwrap(); + let _params: SimpleStructuredParametersWithDefaults = node.declare_parameters("").unwrap(); + println!("{:?}", _params); + } +} diff --git a/rclrs_proc_macros/src/impl.rs b/rclrs_proc_macros/src/impl.rs index 7607669b5..e03fccdfa 100644 --- a/rclrs_proc_macros/src/impl.rs +++ b/rclrs_proc_macros/src/impl.rs @@ -1,6 +1,8 @@ +use std::default; + use proc_macro2::TokenStream; use quote::quote; -use syn::DeriveInput; +use syn::{Data, DeriveInput, Expr, Lit, Meta}; pub(crate) fn derive_struct_parameters(input: DeriveInput) -> syn::Result { let ident = input.ident; @@ -21,6 +23,27 @@ pub(crate) fn derive_struct_parameters(input: DeriveInput) -> syn::Result = None; + + for attr in &f.attrs { + if attr.path().is_ident("param") { + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("default") { + default = Some(meta.value()?.parse()?); + Ok(()) + } else { + syn::Result::Err(syn::Error::new_spanned(meta.path, "Unknown key.")) + } + })?; + } + } + + let default = match default { + Some(expr) => quote! {Some(#expr)}, + None => quote! {None}, + }; + let field_type = match &f.ty { syn::Type::Path(p) => { let mut p = p.path.clone(); @@ -32,17 +55,21 @@ pub(crate) fn derive_struct_parameters(input: DeriveInput) -> syn::Result { return syn::Result::Err(syn::Error::new_spanned( e, - "attribute can only be path type", + "only PathType attributes are supported.", )); } }; let r = quote! { #ident : #field_type::declare_structured( - node, &{match name { - "" => #ident_str.to_string(), - prefix => [prefix, ".", #ident_str].concat(), + rclrs::parameter::structured::ParameterOptions { + node: options.node, + name: &{match options.name { + "" => #ident_str.to_string(), + prefix => [prefix, ".", #ident_str].concat(), + }}, + default: #default, } - })?, + )?, }; args.push(r); } @@ -50,18 +77,20 @@ pub(crate) fn derive_struct_parameters(input: DeriveInput) -> syn::Result() {} + fn assert_parameter() {} #( assert_parameter::<#field_types>(); )* }; - } + } + impl rclrs::parameter::structured::StructuredParametersMeta for #ident { + fn declare_structured_(options: rclrs::parameter::structured::ParameterOptions) + -> core::result::Result { + core::result::Result::Ok(Self{ #(#args)*}) + } - impl rclrs::StructuredParameters for #ident { - fn declare_structured(node: &rclrs::NodeState, name: &str) -> core::result::Result { - core::result::Result::Ok(Self{ #(#args)*}) - } } + impl rclrs::StructuredParameters for #ident {} ); syn::Result::Ok(result) } diff --git a/rclrs_proc_macros/src/lib.rs b/rclrs_proc_macros/src/lib.rs index fcac0543a..7cec9a8eb 100644 --- a/rclrs_proc_macros/src/lib.rs +++ b/rclrs_proc_macros/src/lib.rs @@ -2,7 +2,7 @@ mod r#impl; use proc_macro::TokenStream; use syn::{parse_macro_input, DeriveInput}; -#[proc_macro_derive(StructuredParameters)] +#[proc_macro_derive(StructuredParameters, attributes(param))] pub fn derive_struct_parameters(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); r#impl::derive_struct_parameters(input) From cba5837f157e0cd817a0df19edd87053be55e869 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balthasar=20Sch=C3=BCss?= Date: Sat, 30 Aug 2025 16:52:16 +0200 Subject: [PATCH 03/25] Make declare_parameters public --- rclrs/src/parameter/structured.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rclrs/src/parameter/structured.rs b/rclrs/src/parameter/structured.rs index c538d5999..649692eaf 100644 --- a/rclrs/src/parameter/structured.rs +++ b/rclrs/src/parameter/structured.rs @@ -64,7 +64,7 @@ impl StructuredParametersMeta for crate::Optional } impl NodeState { - fn declare_parameters( + pub fn declare_parameters( &self, name: &str, ) -> core::result::Result From 80ed4979d4838654a556e8e92c1853cb811cb7c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balthasar=20Sch=C3=BCss?= Date: Sat, 30 Aug 2025 16:59:46 +0200 Subject: [PATCH 04/25] Move NodeState from parameter options to enabled default derivation --- rclrs/src/parameter/structured.rs | 33 +++++++++++++++++++------------ rclrs_proc_macros/src/impl.rs | 4 ++-- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/rclrs/src/parameter/structured.rs b/rclrs/src/parameter/structured.rs index 649692eaf..2416505b7 100644 --- a/rclrs/src/parameter/structured.rs +++ b/rclrs/src/parameter/structured.rs @@ -1,34 +1,37 @@ -use crate::NodeState; +use crate::{Node, NodeState}; pub enum DefaultForbidden {} -pub struct ParameterOptions<'a, 'b, T> { - node: &'a crate::NodeState, - name: &'b str, +pub struct ParameterOptions<'a, T> { + name: &'a str, default: Option, } + pub trait StructuredParametersMeta: Sized { fn declare_structured_( + node: &NodeState, options: ParameterOptions, ) -> core::result::Result; } pub trait StructuredParameters: Sized { fn declare_structured( + node: &NodeState, options: ParameterOptions, ) -> core::result::Result where Self: StructuredParametersMeta, { - Self::declare_structured_(options) + Self::declare_structured_(node, options) } } impl StructuredParameters for crate::MandatoryParameter {} impl StructuredParametersMeta for crate::MandatoryParameter { fn declare_structured_( + node: &NodeState, options: ParameterOptions, ) -> core::result::Result { - let builder = options.node.declare_parameter(options.name); + let builder = node.declare_parameter(options.name); let builder = match options.default { Some(default) => builder.default(default), None => builder, @@ -39,9 +42,10 @@ impl StructuredParametersMeta for crate::Mandator impl StructuredParameters for crate::ReadOnlyParameter {} impl StructuredParametersMeta for crate::ReadOnlyParameter { fn declare_structured_( + node: &NodeState, options: ParameterOptions, ) -> core::result::Result { - let builder = options.node.declare_parameter(options.name); + let builder = node.declare_parameter(options.name); let builder = match options.default { Some(default) => builder.default(default), None => builder, @@ -52,9 +56,10 @@ impl StructuredParametersMeta for crate::ReadOnly impl StructuredParameters for crate::OptionalParameter {} impl StructuredParametersMeta for crate::OptionalParameter { fn declare_structured_( + node: &NodeState, options: ParameterOptions, ) -> core::result::Result { - let builder = options.node.declare_parameter(options.name); + let builder = node.declare_parameter(options.name); let builder = match options.default { Some(default) => builder.default(default), None => builder, @@ -71,11 +76,13 @@ impl NodeState { where T: StructuredParameters + StructuredParametersMeta, { - T::declare_structured(ParameterOptions { - node: self, - name: name, - default: None, - }) + T::declare_structured( + self, + ParameterOptions { + name: name, + default: None, + }, + ) } } diff --git a/rclrs_proc_macros/src/impl.rs b/rclrs_proc_macros/src/impl.rs index e03fccdfa..a5d6747a4 100644 --- a/rclrs_proc_macros/src/impl.rs +++ b/rclrs_proc_macros/src/impl.rs @@ -61,8 +61,8 @@ pub(crate) fn derive_struct_parameters(input: DeriveInput) -> syn::Result #ident_str.to_string(), prefix => [prefix, ".", #ident_str].concat(), @@ -84,7 +84,7 @@ pub(crate) fn derive_struct_parameters(input: DeriveInput) -> syn::Result for #ident { - fn declare_structured_(options: rclrs::parameter::structured::ParameterOptions) + fn declare_structured_(node: &rclrs::NodeState, options: rclrs::parameter::structured::ParameterOptions) -> core::result::Result { core::result::Result::Ok(Self{ #(#args)*}) } From 2ba77487f0a6406ea6036b7f65e441c6065e7d8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balthasar=20Sch=C3=BCss?= Date: Sat, 30 Aug 2025 17:11:05 +0200 Subject: [PATCH 05/25] Remove parameter struct --- rclrs/src/parameter/structured.rs | 44 +++++++++++++------------------ rclrs_proc_macros/src/impl.rs | 14 +++++----- 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/rclrs/src/parameter/structured.rs b/rclrs/src/parameter/structured.rs index 2416505b7..156961aea 100644 --- a/rclrs/src/parameter/structured.rs +++ b/rclrs/src/parameter/structured.rs @@ -1,38 +1,36 @@ -use crate::{Node, NodeState}; +use crate::NodeState; pub enum DefaultForbidden {} -pub struct ParameterOptions<'a, T> { - name: &'a str, - default: Option, -} - pub trait StructuredParametersMeta: Sized { fn declare_structured_( node: &NodeState, - options: ParameterOptions, + name: &str, + default: Option, ) -> core::result::Result; } pub trait StructuredParameters: Sized { fn declare_structured( node: &NodeState, - options: ParameterOptions, + name: &str, + default: Option, ) -> core::result::Result where Self: StructuredParametersMeta, { - Self::declare_structured_(node, options) + Self::declare_structured_(node, name, default) } } impl StructuredParameters for crate::MandatoryParameter {} impl StructuredParametersMeta for crate::MandatoryParameter { fn declare_structured_( node: &NodeState, - options: ParameterOptions, + name: &str, + default: Option, ) -> core::result::Result { - let builder = node.declare_parameter(options.name); - let builder = match options.default { + let builder = node.declare_parameter(name); + let builder = match default { Some(default) => builder.default(default), None => builder, }; @@ -43,10 +41,11 @@ impl StructuredParameters for crate::ReadOnlyParamet impl StructuredParametersMeta for crate::ReadOnlyParameter { fn declare_structured_( node: &NodeState, - options: ParameterOptions, + name: &str, + default: Option, ) -> core::result::Result { - let builder = node.declare_parameter(options.name); - let builder = match options.default { + let builder = node.declare_parameter(name); + let builder = match default { Some(default) => builder.default(default), None => builder, }; @@ -57,10 +56,11 @@ impl StructuredParameters for crate::OptionalParamet impl StructuredParametersMeta for crate::OptionalParameter { fn declare_structured_( node: &NodeState, - options: ParameterOptions, + name: &str, + default: Option, ) -> core::result::Result { - let builder = node.declare_parameter(options.name); - let builder = match options.default { + let builder = node.declare_parameter(name); + let builder = match default { Some(default) => builder.default(default), None => builder, }; @@ -76,13 +76,7 @@ impl NodeState { where T: StructuredParameters + StructuredParametersMeta, { - T::declare_structured( - self, - ParameterOptions { - name: name, - default: None, - }, - ) + T::declare_structured(self, name, None) } } diff --git a/rclrs_proc_macros/src/impl.rs b/rclrs_proc_macros/src/impl.rs index a5d6747a4..66e7a7a12 100644 --- a/rclrs_proc_macros/src/impl.rs +++ b/rclrs_proc_macros/src/impl.rs @@ -62,13 +62,11 @@ pub(crate) fn derive_struct_parameters(input: DeriveInput) -> syn::Result #ident_str.to_string(), prefix => [prefix, ".", #ident_str].concat(), - }}, - default: #default, - } + }}, + #default, )?, }; args.push(r); @@ -84,7 +82,11 @@ pub(crate) fn derive_struct_parameters(input: DeriveInput) -> syn::Result for #ident { - fn declare_structured_(node: &rclrs::NodeState, options: rclrs::parameter::structured::ParameterOptions) + fn declare_structured_( + node: &rclrs::NodeState, + name: &str, + default: Option, + ) -> core::result::Result { core::result::Result::Ok(Self{ #(#args)*}) } From 25615498766ded1f3f3d05d2e6306e9ba2ea5e0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balthasar=20Sch=C3=BCss?= Date: Sat, 30 Aug 2025 17:33:04 +0200 Subject: [PATCH 06/25] Add support for description --- rclrs/src/lib.rs | 2 +- rclrs/src/parameter/structured.rs | 38 +++++++++++++++++++++++++++---- rclrs_proc_macros/src/impl.rs | 14 +++++++++++- 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/rclrs/src/lib.rs b/rclrs/src/lib.rs index e19ad2915..083bed388 100644 --- a/rclrs/src/lib.rs +++ b/rclrs/src/lib.rs @@ -213,7 +213,7 @@ pub use error::*; pub use executor::*; pub use logging::*; pub use node::*; -pub use parameter::structured::{ParameterOptions, StructuredParameters, StructuredParametersMeta}; +pub use parameter::structured::{StructuredParameters, StructuredParametersMeta}; pub use parameter::*; pub use publisher::*; pub use qos::*; diff --git a/rclrs/src/parameter/structured.rs b/rclrs/src/parameter/structured.rs index 156961aea..730288aa0 100644 --- a/rclrs/src/parameter/structured.rs +++ b/rclrs/src/parameter/structured.rs @@ -7,6 +7,7 @@ pub trait StructuredParametersMeta: Sized { node: &NodeState, name: &str, default: Option, + description: impl Into>, ) -> core::result::Result; } @@ -15,11 +16,12 @@ pub trait StructuredParameters: Sized { node: &NodeState, name: &str, default: Option, + description: impl Into>, ) -> core::result::Result where Self: StructuredParametersMeta, { - Self::declare_structured_(node, name, default) + Self::declare_structured_(node, name, default, description) } } impl StructuredParameters for crate::MandatoryParameter {} @@ -28,13 +30,14 @@ impl StructuredParametersMeta for crate::Mandator node: &NodeState, name: &str, default: Option, + description: impl Into>, ) -> core::result::Result { let builder = node.declare_parameter(name); let builder = match default { Some(default) => builder.default(default), None => builder, }; - builder.mandatory() + builder.description(description).mandatory() } } impl StructuredParameters for crate::ReadOnlyParameter {} @@ -43,13 +46,14 @@ impl StructuredParametersMeta for crate::ReadOnly node: &NodeState, name: &str, default: Option, + description: impl Into>, ) -> core::result::Result { let builder = node.declare_parameter(name); let builder = match default { Some(default) => builder.default(default), None => builder, }; - builder.read_only() + builder.description(description).read_only() } } impl StructuredParameters for crate::OptionalParameter {} @@ -58,13 +62,14 @@ impl StructuredParametersMeta for crate::Optional node: &NodeState, name: &str, default: Option, + description: impl Into>, ) -> core::result::Result { let builder = node.declare_parameter(name); let builder = match default { Some(default) => builder.default(default), None => builder, }; - builder.optional() + builder.description(description).optional() } } @@ -76,7 +81,7 @@ impl NodeState { where T: StructuredParameters + StructuredParametersMeta, { - T::declare_structured(self, name, None) + T::declare_structured(self, name, None, "") } } @@ -171,4 +176,27 @@ mod tests { let _params: SimpleStructuredParametersWithDefaults = node.declare_parameters("").unwrap(); println!("{:?}", _params); } + #[derive(Debug, StructuredParameters)] + struct SimpleStructuredParametersWithDefaultsAndDescriptions { + #[param(default = 42.0, description = "_mandatory")] + _mandatory: rclrs::MandatoryParameter, + #[param(default = 42.0, description = "_optional")] + _optional: rclrs::OptionalParameter, + #[param(default = Arc::from("test"), description = "_readonly")] + _readonly: rclrs::ReadOnlyParameter>, + } + + #[test] + fn test_simple_structured_parameters_with_defaults_and_descriptions() { + let args: Vec = ["test", "--ros-args"] + .into_iter() + .map(str::to_string) + .collect(); + let context = crate::Context::new(args, rclrs::InitOptions::default()).unwrap(); + let exec = context.create_basic_executor(); + let node = exec.create_node(rclrs::NodeOptions::new("test")).unwrap(); + let _params: SimpleStructuredParametersWithDefaultsAndDescriptions = + node.declare_parameters("").unwrap(); + println!("{:?}", _params); + } } diff --git a/rclrs_proc_macros/src/impl.rs b/rclrs_proc_macros/src/impl.rs index 66e7a7a12..500bda275 100644 --- a/rclrs_proc_macros/src/impl.rs +++ b/rclrs_proc_macros/src/impl.rs @@ -25,6 +25,7 @@ pub(crate) fn derive_struct_parameters(input: DeriveInput) -> syn::Result = None; + let mut description: Option = None; for attr in &f.attrs { if attr.path().is_ident("param") { @@ -32,8 +33,12 @@ pub(crate) fn derive_struct_parameters(input: DeriveInput) -> syn::Result syn::Result quote! {Some(#expr)}, None => quote! {None}, }; + let description = match description { + Some(expr) => quote! {#expr}, + None => quote! {""}, + }; let field_type = match &f.ty { syn::Type::Path(p) => { @@ -67,6 +76,8 @@ pub(crate) fn derive_struct_parameters(input: DeriveInput) -> syn::Result [prefix, ".", #ident_str].concat(), }}, #default, + #description, + )?, }; args.push(r); @@ -86,6 +97,7 @@ pub(crate) fn derive_struct_parameters(input: DeriveInput) -> syn::Result, + description: impl Into>, ) -> core::result::Result { core::result::Result::Ok(Self{ #(#args)*}) From d9bcf97692c67cae59fcfeea4546941dd361bdb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balthasar=20Sch=C3=BCss?= Date: Sat, 30 Aug 2025 19:18:52 +0200 Subject: [PATCH 07/25] Add some documentation --- rclrs/src/parameter.rs | 1 - rclrs/src/parameter/structured.rs | 74 ++++++++++++++++++++++--------- rclrs_proc_macros/src/impl.rs | 6 +-- 3 files changed, 56 insertions(+), 25 deletions(-) diff --git a/rclrs/src/parameter.rs b/rclrs/src/parameter.rs index 73a54ecad..6905944fa 100644 --- a/rclrs/src/parameter.rs +++ b/rclrs/src/parameter.rs @@ -14,7 +14,6 @@ use crate::vendor::rcl_interfaces::msg::rmw::{ParameterType, ParameterValue as R use crate::{ call_string_getter_with_rcl_node, rcl_bindings::*, Node, RclrsError, ENTITY_LIFECYCLE_MUTEX, }; -use std::default; use std::{ collections::{btree_map::Entry, BTreeMap}, fmt::Debug, diff --git a/rclrs/src/parameter/structured.rs b/rclrs/src/parameter/structured.rs index 730288aa0..4904ccdd1 100644 --- a/rclrs/src/parameter/structured.rs +++ b/rclrs/src/parameter/structured.rs @@ -1,29 +1,74 @@ use crate::NodeState; +/// Marker trait for implementing [`StructuredParameters`] where a default value cannot be specified. +/// This is usually the case for container types, that are not represented by any actual parameter. pub enum DefaultForbidden {} -pub trait StructuredParametersMeta: Sized { - fn declare_structured_( +/// Types implementing this trait can delcare their parameters with[`NodeState::declare_parameters`]. +/// The trait can be automatically derived using [`rclrs_proc_macros`] if: +/// - if the type is a struct +/// - all attributes implement [`StructuredParameters`] +pub trait StructuredParameters: Sized { + /// Declares all parameters in ros node. + /// + /// # Parameters + /// + /// - `node`: The ros node to declare parameters for. + /// - `name`: The name of the parameter. Nested parameters are recursively declared with "{name}.{field_name}" if the name is not empty else "{field_name}". + /// - `default`: The default value for the paramter. + /// - `description` The description of the parameter + /// + /// # Returns + /// + /// [`Result`] containing the declared structured parameters or [`crate::DeclarationError`] + fn declare_structured( node: &NodeState, name: &str, default: Option, description: impl Into>, - ) -> core::result::Result; + ) -> core::result::Result + where + Self: StructuredParametersMeta, + { + Self::declare_structured_(node, name, default, description) + } } -pub trait StructuredParameters: Sized { - fn declare_structured( +/// Helper trait to unify the default value type with generic container types like +/// - [`crate::MandatoryParameter`] +/// - [`crate::OptionalParameter`] +/// - [`crate::ReadOnlyParameter`] +/// +/// In these cases the [`Self`] != [`T`] and forces the usage of a generic trait. +/// However, a generic trait also requires annotating this default value in the derive macro. +/// For the container based structured parameters [`T`] is always [`DefaultForbidden`] +/// and therefore we can hide this form the trait + macro by using this helper trait. +/// The previously mentioned leaf types that actually hold parameters, are to be implemented manually anyway. +/// +pub trait StructuredParametersMeta: Sized { + /// See [`StructuredParameters::declare_structured`] + fn declare_structured_( node: &NodeState, name: &str, default: Option, description: impl Into>, - ) -> core::result::Result + ) -> core::result::Result; +} + +impl NodeState { + /// Declares all nested parameters of the [`StructuredParameters`]. + /// Parameter naming recursively follows "`name`.`field_name`" or "`field_name`" if the initial `name` is empty. + pub fn declare_parameters( + &self, + name: &str, + ) -> core::result::Result where - Self: StructuredParametersMeta, + T: StructuredParameters + StructuredParametersMeta, { - Self::declare_structured_(node, name, default, description) + T::declare_structured(self, name, None, "") } } + impl StructuredParameters for crate::MandatoryParameter {} impl StructuredParametersMeta for crate::MandatoryParameter { fn declare_structured_( @@ -73,18 +118,7 @@ impl StructuredParametersMeta for crate::Optional } } -impl NodeState { - pub fn declare_parameters( - &self, - name: &str, - ) -> core::result::Result - where - T: StructuredParameters + StructuredParametersMeta, - { - T::declare_structured(self, name, None, "") - } -} - +#[cfg(test)] mod tests { use std::sync::Arc; diff --git a/rclrs_proc_macros/src/impl.rs b/rclrs_proc_macros/src/impl.rs index 500bda275..7d24b4170 100644 --- a/rclrs_proc_macros/src/impl.rs +++ b/rclrs_proc_macros/src/impl.rs @@ -1,8 +1,6 @@ -use std::default; - use proc_macro2::TokenStream; use quote::quote; -use syn::{Data, DeriveInput, Expr, Lit, Meta}; +use syn::{DeriveInput, Expr}; pub(crate) fn derive_struct_parameters(input: DeriveInput) -> syn::Result { let ident = input.ident; @@ -37,7 +35,7 @@ pub(crate) fn derive_struct_parameters(input: DeriveInput) -> syn::Result Date: Sat, 30 Aug 2025 19:34:51 +0200 Subject: [PATCH 08/25] Add module doc with example --- rclrs/src/parameter/structured.rs | 33 +++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/rclrs/src/parameter/structured.rs b/rclrs/src/parameter/structured.rs index 4904ccdd1..32c3366f9 100644 --- a/rclrs/src/parameter/structured.rs +++ b/rclrs/src/parameter/structured.rs @@ -1,3 +1,36 @@ +//! This module provides the trait [`StructuredParameters`] default implementations for declaring parameters in structured fashion. +//! [`rclrs_proc_macros::StructuredParameters`] provides a macro to derive the trait for structs automatically. +//! +//! # Example +//! ``` +//! use rclrs::*; +//! use rclrs_proc_macros::StructuredParameters; +//! +//! #[derive(StructuredParameters, Debug)] +//! pub struct SimpleStructuredParameters { +//! #[param(description = "optional parameter description")] +//! pub optional: rclrs::OptionalParameter, +//! } +//! #[derive(StructuredParameters, Debug)] +//! pub struct NestedStructuredParameters { +//! pub simple: SimpleStructuredParameters, +//! #[param(default = Arc::from("test"))] +//! pub mandatory: rclrs::MandatoryParameter>, +//! } +//! let args: Vec = [ +//! "test", "--ros-args", +//! "-p", "mandatory:=override", +//! "-p", "simple.optional:=3.14", +//! ].into_iter().map(str::to_string).collect(); +//! let context = crate::Context::new(args, rclrs::InitOptions::default()).unwrap(); +//! let exec = context.create_basic_executor(); +//! let node = exec.create_node(rclrs::NodeOptions::new("test")).unwrap(); +//! let params: NestedStructuredParameters = +//! node.declare_parameters("").unwrap(); +//! let param = params.simple.optional.get(); +//! println!("{:?}", param) +//! ``` + use crate::NodeState; /// Marker trait for implementing [`StructuredParameters`] where a default value cannot be specified. From 2d0be3d8dfa24536d578dfe70445f436323140b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balthasar=20Sch=C3=BCss?= Date: Sat, 30 Aug 2025 20:06:27 +0200 Subject: [PATCH 09/25] Fix format --- rclrs/src/parameter/structured.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rclrs/src/parameter/structured.rs b/rclrs/src/parameter/structured.rs index 32c3366f9..0c6f8314a 100644 --- a/rclrs/src/parameter/structured.rs +++ b/rclrs/src/parameter/structured.rs @@ -217,6 +217,7 @@ mod tests { let context = crate::Context::new(args, rclrs::InitOptions::default()).unwrap(); let exec = context.create_basic_executor(); let node = exec.create_node(rclrs::NodeOptions::new("test")).unwrap(); + let _params: NestedStructuredParameters = node.declare_parameters("nested").unwrap(); println!("{:?}", _params); } From 073322c5ad5702ab01bb382010ffbbcb889d85bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balthasar=20Sch=C3=BCss?= Date: Sat, 30 Aug 2025 20:08:01 +0200 Subject: [PATCH 10/25] Revert changes to build --- Dockerfile | 2 -- rclrs/package.xml | 5 ++--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0d500fcd9..99ed6ac5d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,8 +9,6 @@ RUN apt-get update && apt-get install -y \ libclang-dev \ tmux \ python3-pip \ - ros-humble-test-msgs \ - ros-humble-example-interfaces \ && rm -rf /var/lib/apt/lists/* # Install Rust diff --git a/rclrs/package.xml b/rclrs/package.xml index 8d91b2de8..d947bbf8f 100644 --- a/rclrs/package.xml +++ b/rclrs/package.xml @@ -19,10 +19,9 @@ builtin_interfaces rcl_interfaces rosgraph_msgs - rosidl_default_generators - test_msgs - example_interfaces + test_msgs + example_interfaces ament_cargo From 4a07678732f21ceac79cd4e2d891b3606a7aa82c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balthasar=20Sch=C3=BCss?= Date: Sat, 30 Aug 2025 20:14:45 +0200 Subject: [PATCH 11/25] Fix error message punctuation --- rclrs_proc_macros/src/impl.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rclrs_proc_macros/src/impl.rs b/rclrs_proc_macros/src/impl.rs index 7d24b4170..fc20b1d0d 100644 --- a/rclrs_proc_macros/src/impl.rs +++ b/rclrs_proc_macros/src/impl.rs @@ -10,7 +10,7 @@ pub(crate) fn derive_struct_parameters(input: DeriveInput) -> syn::Result { return syn::Result::Err(syn::Error::new_spanned( ident, - "StrucutredParameter trait can only be derived for structs", + "StructuredParameters trait can only be derived for structs", )); } }; @@ -62,7 +62,7 @@ pub(crate) fn derive_struct_parameters(input: DeriveInput) -> syn::Result { return syn::Result::Err(syn::Error::new_spanned( e, - "only PathType attributes are supported.", + "Only PathType attributes are supported", )); } }; From 620d09d8d1538342624477eb264a1f3d566bc77c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balthasar=20Sch=C3=BCss?= Date: Sat, 30 Aug 2025 20:24:48 +0200 Subject: [PATCH 12/25] cargo fmt nightly --- rclrs/src/lib.rs | 6 ++++-- rclrs/src/parameter/structured.rs | 3 +-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/rclrs/src/lib.rs b/rclrs/src/lib.rs index 083bed388..542f0c2ec 100644 --- a/rclrs/src/lib.rs +++ b/rclrs/src/lib.rs @@ -213,8 +213,10 @@ pub use error::*; pub use executor::*; pub use logging::*; pub use node::*; -pub use parameter::structured::{StructuredParameters, StructuredParametersMeta}; -pub use parameter::*; +pub use parameter::{ + structured::{StructuredParameters, StructuredParametersMeta}, + *, +}; pub use publisher::*; pub use qos::*; pub use rcl_bindings::rmw_request_id_t; diff --git a/rclrs/src/parameter/structured.rs b/rclrs/src/parameter/structured.rs index 0c6f8314a..b22dbca86 100644 --- a/rclrs/src/parameter/structured.rs +++ b/rclrs/src/parameter/structured.rs @@ -156,8 +156,7 @@ mod tests { use std::sync::Arc; use crate as rclrs; - use rclrs::parameter::structured::*; - use rclrs::CreateBasicExecutor; + use rclrs::{parameter::structured::*, CreateBasicExecutor}; use rclrs_proc_macros::StructuredParameters; #[derive(StructuredParameters, Debug)] From ba3d941ab0693063ab384eee9063bc67124a58f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balthasar=20Sch=C3=BCss?= Date: Tue, 2 Sep 2025 16:21:48 +0200 Subject: [PATCH 13/25] Remove docstring test --- rclrs/src/parameter/structured.rs | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/rclrs/src/parameter/structured.rs b/rclrs/src/parameter/structured.rs index b22dbca86..074aec312 100644 --- a/rclrs/src/parameter/structured.rs +++ b/rclrs/src/parameter/structured.rs @@ -1,35 +1,6 @@ //! This module provides the trait [`StructuredParameters`] default implementations for declaring parameters in structured fashion. //! [`rclrs_proc_macros::StructuredParameters`] provides a macro to derive the trait for structs automatically. //! -//! # Example -//! ``` -//! use rclrs::*; -//! use rclrs_proc_macros::StructuredParameters; -//! -//! #[derive(StructuredParameters, Debug)] -//! pub struct SimpleStructuredParameters { -//! #[param(description = "optional parameter description")] -//! pub optional: rclrs::OptionalParameter, -//! } -//! #[derive(StructuredParameters, Debug)] -//! pub struct NestedStructuredParameters { -//! pub simple: SimpleStructuredParameters, -//! #[param(default = Arc::from("test"))] -//! pub mandatory: rclrs::MandatoryParameter>, -//! } -//! let args: Vec = [ -//! "test", "--ros-args", -//! "-p", "mandatory:=override", -//! "-p", "simple.optional:=3.14", -//! ].into_iter().map(str::to_string).collect(); -//! let context = crate::Context::new(args, rclrs::InitOptions::default()).unwrap(); -//! let exec = context.create_basic_executor(); -//! let node = exec.create_node(rclrs::NodeOptions::new("test")).unwrap(); -//! let params: NestedStructuredParameters = -//! node.declare_parameters("").unwrap(); -//! let param = params.simple.optional.get(); -//! println!("{:?}", param) -//! ``` use crate::NodeState; From 6729752160a554d700f710bc4bc73496e962e916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balthasar=20Sch=C3=BCss?= Date: Tue, 2 Sep 2025 16:22:15 +0200 Subject: [PATCH 14/25] Only require macros at test time This way they can remain part of the crate without breaking other ros package builds Other packages can load the dependency through cargo --- rclrs/Cargo.toml | 2 +- rclrs/src/lib.rs | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/rclrs/Cargo.toml b/rclrs/Cargo.toml index b33881d06..feef1bf7c 100644 --- a/rclrs/Cargo.toml +++ b/rclrs/Cargo.toml @@ -36,13 +36,13 @@ rosidl_runtime_rs = "0.4" serde = { version = "1", optional = true, features = ["derive"] } serde-big-array = { version = "0.5.1", optional = true } -rclrs_proc_macros = {path = "../rclrs_proc_macros"} [dev-dependencies] # Needed for e.g. writing yaml files in tests tempfile = "3.3.0" # Needed for parameter service tests tokio = { version = "1", features = ["rt", "time", "macros"] } +rclrs_proc_macros = {path = "../rclrs_proc_macros"} [build-dependencies] # Needed for uploading documentation to docs.rs diff --git a/rclrs/src/lib.rs b/rclrs/src/lib.rs index 542f0c2ec..787a55794 100644 --- a/rclrs/src/lib.rs +++ b/rclrs/src/lib.rs @@ -225,6 +225,4 @@ pub use subscription::*; pub use time::*; use time_source::*; pub use wait_set::*; -pub use worker::*; - -pub use rclrs_proc_macros::StructuredParameters; +pub use worker::*; \ No newline at end of file From b07fd43df4f757cef1310c1ba2813b799933b698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balthasar=20Sch=C3=BCss?= Date: Fri, 19 Sep 2025 13:38:25 +0200 Subject: [PATCH 15/25] Add setup to bashrc --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 99ed6ac5d..73bad92eb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,7 +22,7 @@ COPY src/ros2_rust/docker/rosidl_rust_setup.sh / RUN ./rosidl_rust_setup.sh RUN mkdir -p /workspace && echo "Did you forget to mount the repository into the Docker container?" > /workspace/HELLO.txt -RUN echo -e "\nsource /opt/ros/${ROS_DISTRO}/setup.sh" +RUN echo -e "\nsource /opt/ros/${ROS_DISTRO}/setup.sh" >> /root/.bashrc WORKDIR /workspace From e760adf579ca123253ecb8130d084e6f0cff7156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balthasar=20Sch=C3=BCss?= Date: Fri, 19 Sep 2025 13:47:09 +0200 Subject: [PATCH 16/25] Fix bug in Dockerfile remove -e flag from echo to not add it to bashrc --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 73bad92eb..a15aab292 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,7 +22,7 @@ COPY src/ros2_rust/docker/rosidl_rust_setup.sh / RUN ./rosidl_rust_setup.sh RUN mkdir -p /workspace && echo "Did you forget to mount the repository into the Docker container?" > /workspace/HELLO.txt -RUN echo -e "\nsource /opt/ros/${ROS_DISTRO}/setup.sh" >> /root/.bashrc +RUN echo "\nsource /opt/ros/${ROS_DISTRO}/setup.sh" >> /root/.bashrc WORKDIR /workspace From 36de62a236341dd5fe82b1b5dfd7e64149870bda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balthasar=20Sch=C3=BCss?= Date: Fri, 19 Sep 2025 11:57:19 +0000 Subject: [PATCH 17/25] Fix cargo fmt --- rclrs/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rclrs/src/lib.rs b/rclrs/src/lib.rs index 787a55794..96a0a5663 100644 --- a/rclrs/src/lib.rs +++ b/rclrs/src/lib.rs @@ -225,4 +225,4 @@ pub use subscription::*; pub use time::*; use time_source::*; pub use wait_set::*; -pub use worker::*; \ No newline at end of file +pub use worker::*; From 46652639ccbcec54d1b93e35e8977a18bf03fa09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balthasar=20Sch=C3=BCss?= Date: Fri, 19 Sep 2025 12:24:51 +0000 Subject: [PATCH 18/25] Follow standard macro package naming convention --- Cargo.toml | 2 +- {rclrs_proc_macros => rclrs-macros}/Cargo.toml | 2 +- {rclrs_proc_macros => rclrs-macros}/src/impl.rs | 0 {rclrs_proc_macros => rclrs-macros}/src/lib.rs | 0 rclrs/Cargo.toml | 2 +- rclrs/src/parameter/structured.rs | 2 +- 6 files changed, 4 insertions(+), 4 deletions(-) rename {rclrs_proc_macros => rclrs-macros}/Cargo.toml (92%) rename {rclrs_proc_macros => rclrs-macros}/src/impl.rs (100%) rename {rclrs_proc_macros => rclrs-macros}/src/lib.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 0fb0e9a99..ad2071805 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] members = [ "rclrs", - "rclrs_proc_macros", + "rclrs-macros", ] resolver = "2" diff --git a/rclrs_proc_macros/Cargo.toml b/rclrs-macros/Cargo.toml similarity index 92% rename from rclrs_proc_macros/Cargo.toml rename to rclrs-macros/Cargo.toml index 51ee68bac..dbf19b006 100644 --- a/rclrs_proc_macros/Cargo.toml +++ b/rclrs-macros/Cargo.toml @@ -1,5 +1,5 @@ [package] -name= "rclrs_proc_macros" +name= "rclrs-macros" version = "0.0.1" authors = ["Balthasar Schüss "] edition = "2021" diff --git a/rclrs_proc_macros/src/impl.rs b/rclrs-macros/src/impl.rs similarity index 100% rename from rclrs_proc_macros/src/impl.rs rename to rclrs-macros/src/impl.rs diff --git a/rclrs_proc_macros/src/lib.rs b/rclrs-macros/src/lib.rs similarity index 100% rename from rclrs_proc_macros/src/lib.rs rename to rclrs-macros/src/lib.rs diff --git a/rclrs/Cargo.toml b/rclrs/Cargo.toml index feef1bf7c..4d62337b9 100644 --- a/rclrs/Cargo.toml +++ b/rclrs/Cargo.toml @@ -42,7 +42,7 @@ serde-big-array = { version = "0.5.1", optional = true } tempfile = "3.3.0" # Needed for parameter service tests tokio = { version = "1", features = ["rt", "time", "macros"] } -rclrs_proc_macros = {path = "../rclrs_proc_macros"} +rclrs-macros = {path = "../rclrs-macros"} [build-dependencies] # Needed for uploading documentation to docs.rs diff --git a/rclrs/src/parameter/structured.rs b/rclrs/src/parameter/structured.rs index 074aec312..c370f5409 100644 --- a/rclrs/src/parameter/structured.rs +++ b/rclrs/src/parameter/structured.rs @@ -128,7 +128,7 @@ mod tests { use crate as rclrs; use rclrs::{parameter::structured::*, CreateBasicExecutor}; - use rclrs_proc_macros::StructuredParameters; + use rclrs_macros::StructuredParameters; #[derive(StructuredParameters, Debug)] struct SimpleStructuredParameters { From 5f93232c77bad0f27c85633e89bc53978ef119c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balthasar=20Sch=C3=BCss?= Date: Fri, 19 Sep 2025 14:40:47 +0200 Subject: [PATCH 19/25] Prepare macro to implement enum derive --- rclrs-macros/src/impl.rs | 15 ++++++++++----- rclrs-macros/src/lib.rs | 4 ++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/rclrs-macros/src/impl.rs b/rclrs-macros/src/impl.rs index fc20b1d0d..743901ee8 100644 --- a/rclrs-macros/src/impl.rs +++ b/rclrs-macros/src/impl.rs @@ -2,21 +2,26 @@ use proc_macro2::TokenStream; use quote::quote; use syn::{DeriveInput, Expr}; -pub(crate) fn derive_struct_parameters(input: DeriveInput) -> syn::Result { +pub(crate) fn derive_structured_parameters(input: DeriveInput) -> syn::Result { let ident = input.ident; - let fields = match input.data { - syn::Data::Struct(ref s) => &s.fields, + match input.data { + syn::Data::Struct(ref s) => derive_structured_parameters_struct(ident, s), _ => { return syn::Result::Err(syn::Error::new_spanned( ident, "StructuredParameters trait can only be derived for structs", )); } - }; + } +} +pub(crate) fn derive_structured_parameters_struct( + ident: proc_macro2::Ident, + struct_: &syn::DataStruct, +) -> syn::Result { + let fields = &struct_.fields; let field_types: Vec<_> = fields.iter().map(|f| &f.ty).collect(); - let mut args = Vec::new(); for f in fields { let ident = f.ident.as_ref().unwrap(); diff --git a/rclrs-macros/src/lib.rs b/rclrs-macros/src/lib.rs index 7cec9a8eb..7daf128dd 100644 --- a/rclrs-macros/src/lib.rs +++ b/rclrs-macros/src/lib.rs @@ -3,9 +3,9 @@ use proc_macro::TokenStream; use syn::{parse_macro_input, DeriveInput}; #[proc_macro_derive(StructuredParameters, attributes(param))] -pub fn derive_struct_parameters(input: TokenStream) -> TokenStream { +pub fn derive_structured_parameters(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); - r#impl::derive_struct_parameters(input) + r#impl::derive_structured_parameters(input) .unwrap_or_else(|e| e.to_compile_error()) .into() } From 0879517e8fcda89a468588f33b4253fec50762d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balthasar=20Sch=C3=BCss?= Date: Fri, 19 Sep 2025 14:45:22 +0200 Subject: [PATCH 20/25] Reduce visibility of struct macro implementation --- rclrs-macros/src/impl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rclrs-macros/src/impl.rs b/rclrs-macros/src/impl.rs index 743901ee8..0fe9bd0d8 100644 --- a/rclrs-macros/src/impl.rs +++ b/rclrs-macros/src/impl.rs @@ -15,7 +15,7 @@ pub(crate) fn derive_structured_parameters(input: DeriveInput) -> syn::Result syn::Result { From 74cf5111a6f6c2e5b6639337d30dbcd4c7b0c088 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balthasar=20Sch=C3=BCss?= Date: Fri, 19 Sep 2025 15:57:24 +0200 Subject: [PATCH 21/25] Extend macro with more builder options --- rclrs-macros/src/impl.rs | 25 ++++++++++- rclrs/src/parameter/structured.rs | 72 ++++++++++++++++++++++++++++--- 2 files changed, 90 insertions(+), 7 deletions(-) diff --git a/rclrs-macros/src/impl.rs b/rclrs-macros/src/impl.rs index 0fe9bd0d8..0d4eebbd6 100644 --- a/rclrs-macros/src/impl.rs +++ b/rclrs-macros/src/impl.rs @@ -1,6 +1,6 @@ use proc_macro2::TokenStream; use quote::quote; -use syn::{DeriveInput, Expr}; +use syn::{token::Token, DeriveInput, Expr}; pub(crate) fn derive_structured_parameters(input: DeriveInput) -> syn::Result { let ident = input.ident; @@ -15,6 +15,7 @@ pub(crate) fn derive_structured_parameters(input: DeriveInput) -> syn::Result = None; let mut description: Option = None; + let mut constraints: Option = None; + let mut ignore_override = false; + let mut discard_mismatching_prior_value = false; for attr in &f.attrs { if attr.path().is_ident("param") { @@ -39,6 +43,15 @@ fn derive_structured_parameters_struct( } else if meta.path.is_ident("description") { description = Some(meta.value()?.parse()?); Ok(()) + } else if meta.path.is_ident("constraints") { + constraints = Some(meta.value()?.parse()?); + Ok(()) + } else if meta.path.is_ident("ignore_override") { + ignore_override = true; + Ok(()) + } else if meta.path.is_ident("discard_mismatching_prior_value") { + discard_mismatching_prior_value = true; + Ok(()) } else { let err = format!("Unknown key: {:?}", &meta.path.get_ident()); syn::Result::Err(syn::Error::new_spanned(meta.path, err)) @@ -55,6 +68,10 @@ fn derive_structured_parameters_struct( Some(expr) => quote! {#expr}, None => quote! {""}, }; + let constraints = match constraints { + Some(expr) => quote! {#expr}, + None => quote! {""}, + }; let field_type = match &f.ty { syn::Type::Path(p) => { @@ -80,6 +97,9 @@ fn derive_structured_parameters_struct( }}, #default, #description, + #constraints, + #ignore_override, + #discard_mismatching_prior_value, )?, }; @@ -101,6 +121,9 @@ fn derive_structured_parameters_struct( name: &str, default: Option, description: impl Into>, + constraints: impl Into>, + ignore_override: bool, + discard_mismatching_prior_value: bool, ) -> core::result::Result { core::result::Result::Ok(Self{ #(#args)*}) diff --git a/rclrs/src/parameter/structured.rs b/rclrs/src/parameter/structured.rs index c370f5409..2d57556d2 100644 --- a/rclrs/src/parameter/structured.rs +++ b/rclrs/src/parameter/structured.rs @@ -30,11 +30,22 @@ pub trait StructuredParameters: Sized { name: &str, default: Option, description: impl Into>, + constraints: impl Into>, + ignore_override: bool, + discard_mismatching_prior_value: bool, ) -> core::result::Result where Self: StructuredParametersMeta, { - Self::declare_structured_(node, name, default, description) + Self::declare_structured_( + node, + name, + default, + description, + constraints, + ignore_override, + discard_mismatching_prior_value, + ) } } @@ -56,6 +67,9 @@ pub trait StructuredParametersMeta: Sized { name: &str, default: Option, description: impl Into>, + constraints: impl Into>, + ignore_override: bool, + discard_mismatching_prior_value: bool, ) -> core::result::Result; } @@ -69,7 +83,7 @@ impl NodeState { where T: StructuredParameters + StructuredParametersMeta, { - T::declare_structured(self, name, None, "") + T::declare_structured(self, name, None, "", "", false, false) } } @@ -80,13 +94,27 @@ impl StructuredParametersMeta for crate::Mandator name: &str, default: Option, description: impl Into>, + constraints: impl Into>, + ignore_override: bool, + discard_mismatching_prior_value: bool, ) -> core::result::Result { let builder = node.declare_parameter(name); let builder = match default { Some(default) => builder.default(default), None => builder, }; - builder.description(description).mandatory() + let builder = match ignore_override { + true => builder.ignore_override(), + false => builder, + }; + let builder = match discard_mismatching_prior_value { + true => builder.discard_mismatching_prior_value(), + false => builder, + }; + builder + .description(description) + .constraints(constraints) + .mandatory() } } impl StructuredParameters for crate::ReadOnlyParameter {} @@ -96,13 +124,27 @@ impl StructuredParametersMeta for crate::ReadOnly name: &str, default: Option, description: impl Into>, + constraints: impl Into>, + ignore_override: bool, + discard_mismatching_prior_value: bool, ) -> core::result::Result { let builder = node.declare_parameter(name); let builder = match default { Some(default) => builder.default(default), None => builder, }; - builder.description(description).read_only() + let builder = match ignore_override { + true => builder.ignore_override(), + false => builder, + }; + let builder = match discard_mismatching_prior_value { + true => builder.discard_mismatching_prior_value(), + false => builder, + }; + builder + .description(description) + .constraints(constraints) + .read_only() } } impl StructuredParameters for crate::OptionalParameter {} @@ -112,13 +154,31 @@ impl StructuredParametersMeta for crate::Optional name: &str, default: Option, description: impl Into>, + constraints: impl Into>, + ignore_override: bool, + discard_mismatching_prior_value: bool, ) -> core::result::Result { let builder = node.declare_parameter(name); let builder = match default { Some(default) => builder.default(default), None => builder, }; - builder.description(description).optional() + let builder = match ignore_override { + true => builder.ignore_override(), + false => builder, + }; + let builder = match discard_mismatching_prior_value { + true => builder.discard_mismatching_prior_value(), + false => builder, + }; + + //builder.discriminate(f) + //builder.range(range) + + builder + .description(description) + .constraints(constraints) + .optional() } } @@ -216,7 +276,7 @@ mod tests { } #[derive(Debug, StructuredParameters)] struct SimpleStructuredParametersWithDefaultsAndDescriptions { - #[param(default = 42.0, description = "_mandatory")] + #[param(default = 42.0, ignore_override, description = "_mandatory")] _mandatory: rclrs::MandatoryParameter, #[param(default = 42.0, description = "_optional")] _optional: rclrs::OptionalParameter, From 661698807aca8ce8832a254f6243eb109b6110f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balthasar=20Sch=C3=BCss?= Date: Fri, 19 Sep 2025 16:20:32 +0200 Subject: [PATCH 22/25] support discimator function --- rclrs-macros/src/impl.rs | 20 ++++++++++++++++---- rclrs/src/parameter/structured.rs | 25 +++++++++++++++++++++---- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/rclrs-macros/src/impl.rs b/rclrs-macros/src/impl.rs index 0d4eebbd6..f0e4882b5 100644 --- a/rclrs-macros/src/impl.rs +++ b/rclrs-macros/src/impl.rs @@ -33,6 +33,7 @@ fn derive_structured_parameters_struct( let mut constraints: Option = None; let mut ignore_override = false; let mut discard_mismatching_prior_value = false; + let mut discriminate: Option = None; for attr in &f.attrs { if attr.path().is_ident("param") { @@ -52,6 +53,9 @@ fn derive_structured_parameters_struct( } else if meta.path.is_ident("discard_mismatching_prior_value") { discard_mismatching_prior_value = true; Ok(()) + } else if meta.path.is_ident("discriminate") { + discriminate = Some(meta.value()?.parse()?); + Ok(()) } else { let err = format!("Unknown key: {:?}", &meta.path.get_ident()); syn::Result::Err(syn::Error::new_spanned(meta.path, err)) @@ -72,6 +76,12 @@ fn derive_structured_parameters_struct( Some(expr) => quote! {#expr}, None => quote! {""}, }; + let discriminate = match discriminate { + Some(expr) => quote! { + core::option::Option::Some(Box::new(#expr)) + }, + None => quote! {core::option::Option::None}, + }; let field_type = match &f.ty { syn::Type::Path(p) => { @@ -100,6 +110,7 @@ fn derive_structured_parameters_struct( #constraints, #ignore_override, #discard_mismatching_prior_value, + #discriminate, )?, }; @@ -119,13 +130,14 @@ fn derive_structured_parameters_struct( fn declare_structured_( node: &rclrs::NodeState, name: &str, - default: Option, - description: impl Into>, - constraints: impl Into>, + default: core::option::Option, + description: impl core::convert::Into>, + constraints: impl core::convert::Into>, ignore_override: bool, discard_mismatching_prior_value: bool, + discriminate: core::option::Option) -> core::option::Option>>, ) - -> core::result::Result { + -> core::result::Result { core::result::Result::Ok(Self{ #(#args)*}) } diff --git a/rclrs/src/parameter/structured.rs b/rclrs/src/parameter/structured.rs index 2d57556d2..6c240e8bb 100644 --- a/rclrs/src/parameter/structured.rs +++ b/rclrs/src/parameter/structured.rs @@ -33,6 +33,7 @@ pub trait StructuredParameters: Sized { constraints: impl Into>, ignore_override: bool, discard_mismatching_prior_value: bool, + discriminate: Option) -> Option>>, ) -> core::result::Result where Self: StructuredParametersMeta, @@ -45,6 +46,7 @@ pub trait StructuredParameters: Sized { constraints, ignore_override, discard_mismatching_prior_value, + discriminate, ) } } @@ -70,6 +72,7 @@ pub trait StructuredParametersMeta: Sized { constraints: impl Into>, ignore_override: bool, discard_mismatching_prior_value: bool, + discriminate: Option) -> Option>>, ) -> core::result::Result; } @@ -83,7 +86,7 @@ impl NodeState { where T: StructuredParameters + StructuredParametersMeta, { - T::declare_structured(self, name, None, "", "", false, false) + T::declare_structured(self, name, None, "", "", false, false, None) } } @@ -97,6 +100,7 @@ impl StructuredParametersMeta for crate::Mandator constraints: impl Into>, ignore_override: bool, discard_mismatching_prior_value: bool, + discriminate: Option) -> Option>>, ) -> core::result::Result { let builder = node.declare_parameter(name); let builder = match default { @@ -111,6 +115,12 @@ impl StructuredParametersMeta for crate::Mandator true => builder.discard_mismatching_prior_value(), false => builder, }; + let builder = match discriminate { + Some(f) => builder.discriminate(f), + None => builder, + }; + //builder.range(range) + builder .description(description) .constraints(constraints) @@ -127,6 +137,7 @@ impl StructuredParametersMeta for crate::ReadOnly constraints: impl Into>, ignore_override: bool, discard_mismatching_prior_value: bool, + discriminate: Option) -> Option>>, ) -> core::result::Result { let builder = node.declare_parameter(name); let builder = match default { @@ -141,6 +152,10 @@ impl StructuredParametersMeta for crate::ReadOnly true => builder.discard_mismatching_prior_value(), false => builder, }; + let builder = match discriminate { + Some(f) => builder.discriminate(f), + None => builder, + }; builder .description(description) .constraints(constraints) @@ -157,6 +172,7 @@ impl StructuredParametersMeta for crate::Optional constraints: impl Into>, ignore_override: bool, discard_mismatching_prior_value: bool, + discriminate: Option) -> Option>>, ) -> core::result::Result { let builder = node.declare_parameter(name); let builder = match default { @@ -171,9 +187,10 @@ impl StructuredParametersMeta for crate::Optional true => builder.discard_mismatching_prior_value(), false => builder, }; - - //builder.discriminate(f) - //builder.range(range) + let builder = match discriminate { + Some(f) => builder.discriminate(f), + None => builder, + }; builder .description(description) From e688869a13aedb3206461fe1e3b51f7f0276a81e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balthasar=20Sch=C3=BCss?= Date: Fri, 19 Sep 2025 16:22:40 +0200 Subject: [PATCH 23/25] Fix bug --- rclrs-macros/src/impl.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rclrs-macros/src/impl.rs b/rclrs-macros/src/impl.rs index f0e4882b5..f68a6ea70 100644 --- a/rclrs-macros/src/impl.rs +++ b/rclrs-macros/src/impl.rs @@ -135,7 +135,10 @@ fn derive_structured_parameters_struct( constraints: impl core::convert::Into>, ignore_override: bool, discard_mismatching_prior_value: bool, - discriminate: core::option::Option) -> core::option::Option>>, + discriminate: core::option::Option) + -> core::option::Option>>, ) -> core::result::Result { core::result::Result::Ok(Self{ #(#args)*}) From 22a022a3751ba1be7718c63b7cbca451e651de31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balthasar=20Sch=C3=BCss?= Date: Fri, 19 Sep 2025 16:56:49 +0200 Subject: [PATCH 24/25] Add support for ranges --- rclrs-macros/src/impl.rs | 21 +++++++++--- rclrs/src/parameter/structured.rs | 56 +++++++++++++++++++++++++++---- 2 files changed, 65 insertions(+), 12 deletions(-) diff --git a/rclrs-macros/src/impl.rs b/rclrs-macros/src/impl.rs index f68a6ea70..34d3828be 100644 --- a/rclrs-macros/src/impl.rs +++ b/rclrs-macros/src/impl.rs @@ -1,6 +1,6 @@ use proc_macro2::TokenStream; use quote::quote; -use syn::{token::Token, DeriveInput, Expr}; +use syn::{DeriveInput, Expr}; pub(crate) fn derive_structured_parameters(input: DeriveInput) -> syn::Result { let ident = input.ident; @@ -34,6 +34,7 @@ fn derive_structured_parameters_struct( let mut ignore_override = false; let mut discard_mismatching_prior_value = false; let mut discriminate: Option = None; + let mut range: Option = None; for attr in &f.attrs { if attr.path().is_ident("param") { @@ -56,6 +57,9 @@ fn derive_structured_parameters_struct( } else if meta.path.is_ident("discriminate") { discriminate = Some(meta.value()?.parse()?); Ok(()) + } else if meta.path.is_ident("range") { + range = Some(meta.value()?.parse()?); + Ok(()) } else { let err = format!("Unknown key: {:?}", &meta.path.get_ident()); syn::Result::Err(syn::Error::new_spanned(meta.path, err)) @@ -82,6 +86,12 @@ fn derive_structured_parameters_struct( }, None => quote! {core::option::Option::None}, }; + let range = match range { + Some(expr) => quote! { + core::option::Option::Some(#expr) + }, + None => quote! {core::option::Option::None}, + }; let field_type = match &f.ty { syn::Type::Path(p) => { @@ -111,7 +121,7 @@ fn derive_structured_parameters_struct( #ignore_override, #discard_mismatching_prior_value, #discriminate, - + #range, )?, }; args.push(r); @@ -130,15 +140,16 @@ fn derive_structured_parameters_struct( fn declare_structured_( node: &rclrs::NodeState, name: &str, - default: core::option::Option, + default: core::option::Option, description: impl core::convert::Into>, constraints: impl core::convert::Into>, ignore_override: bool, discard_mismatching_prior_value: bool, discriminate: core::option::Option) - -> core::option::Option) + -> core::option::Option>>, + range: core::option::Option<::Range>, ) -> core::result::Result { core::result::Result::Ok(Self{ #(#args)*}) diff --git a/rclrs/src/parameter/structured.rs b/rclrs/src/parameter/structured.rs index 6c240e8bb..435eaae1d 100644 --- a/rclrs/src/parameter/structured.rs +++ b/rclrs/src/parameter/structured.rs @@ -4,9 +4,36 @@ use crate::NodeState; +use super::ParameterVariant; + /// Marker trait for implementing [`StructuredParameters`] where a default value cannot be specified. /// This is usually the case for container types, that are not represented by any actual parameter. + +#[derive(Clone, Copy)] pub enum DefaultForbidden {} +impl crate::ParameterVariant for DefaultForbidden { + type Range = (); + + fn kind() -> crate::ParameterKind { + // cannot be instantiated cannot be called + // let's satisfy the type checker + unreachable!() + } +} +impl From for crate::ParameterValue { + fn from(_value: DefaultForbidden) -> Self { + // cannot be instantiated cannot be called + // let's satisfy the type checker + unreachable!() + } +} +impl From for DefaultForbidden { + fn from(_value: crate::ParameterValue) -> Self { + // cannot be instantiated cannot be called + // let's satisfy the type checker + unreachable!() + } +} /// Types implementing this trait can delcare their parameters with[`NodeState::declare_parameters`]. /// The trait can be automatically derived using [`rclrs_proc_macros`] if: @@ -25,7 +52,7 @@ pub trait StructuredParameters: Sized { /// # Returns /// /// [`Result`] containing the declared structured parameters or [`crate::DeclarationError`] - fn declare_structured( + fn declare_structured( node: &NodeState, name: &str, default: Option, @@ -34,6 +61,7 @@ pub trait StructuredParameters: Sized { ignore_override: bool, discard_mismatching_prior_value: bool, discriminate: Option) -> Option>>, + range: Option<::Range>, ) -> core::result::Result where Self: StructuredParametersMeta, @@ -47,6 +75,7 @@ pub trait StructuredParameters: Sized { ignore_override, discard_mismatching_prior_value, discriminate, + range, ) } } @@ -62,7 +91,7 @@ pub trait StructuredParameters: Sized { /// and therefore we can hide this form the trait + macro by using this helper trait. /// The previously mentioned leaf types that actually hold parameters, are to be implemented manually anyway. /// -pub trait StructuredParametersMeta: Sized { +pub trait StructuredParametersMeta: Sized { /// See [`StructuredParameters::declare_structured`] fn declare_structured_( node: &NodeState, @@ -73,20 +102,21 @@ pub trait StructuredParametersMeta: Sized { ignore_override: bool, discard_mismatching_prior_value: bool, discriminate: Option) -> Option>>, + range: Option<::Range>, ) -> core::result::Result; } impl NodeState { /// Declares all nested parameters of the [`StructuredParameters`]. /// Parameter naming recursively follows "`name`.`field_name`" or "`field_name`" if the initial `name` is empty. - pub fn declare_parameters( + pub fn declare_parameters( &self, name: &str, ) -> core::result::Result where T: StructuredParameters + StructuredParametersMeta, { - T::declare_structured(self, name, None, "", "", false, false, None) + T::declare_structured(self, name, None, "", "", false, false, None, None) } } @@ -101,6 +131,7 @@ impl StructuredParametersMeta for crate::Mandator ignore_override: bool, discard_mismatching_prior_value: bool, discriminate: Option) -> Option>>, + range: Option<::Range>, ) -> core::result::Result { let builder = node.declare_parameter(name); let builder = match default { @@ -119,8 +150,10 @@ impl StructuredParametersMeta for crate::Mandator Some(f) => builder.discriminate(f), None => builder, }; - //builder.range(range) - + let builder = match range { + Some(range) => builder.range(range), + None => builder, + }; builder .description(description) .constraints(constraints) @@ -138,6 +171,7 @@ impl StructuredParametersMeta for crate::ReadOnly ignore_override: bool, discard_mismatching_prior_value: bool, discriminate: Option) -> Option>>, + range: Option<::Range>, ) -> core::result::Result { let builder = node.declare_parameter(name); let builder = match default { @@ -156,6 +190,10 @@ impl StructuredParametersMeta for crate::ReadOnly Some(f) => builder.discriminate(f), None => builder, }; + let builder = match range { + Some(range) => builder.range(range), + None => builder, + }; builder .description(description) .constraints(constraints) @@ -173,6 +211,7 @@ impl StructuredParametersMeta for crate::Optional ignore_override: bool, discard_mismatching_prior_value: bool, discriminate: Option) -> Option>>, + range: Option<::Range>, ) -> core::result::Result { let builder = node.declare_parameter(name); let builder = match default { @@ -191,7 +230,10 @@ impl StructuredParametersMeta for crate::Optional Some(f) => builder.discriminate(f), None => builder, }; - + let builder = match range { + Some(range) => builder.range(range), + None => builder, + }; builder .description(description) .constraints(constraints) From e13ed4b412f07b482789ed33c4e612693146ffcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balthasar=20Sch=C3=BCss?= Date: Wed, 24 Sep 2025 16:03:25 +0200 Subject: [PATCH 25/25] Add tests for all macro options --- rclrs/src/parameter/structured.rs | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/rclrs/src/parameter/structured.rs b/rclrs/src/parameter/structured.rs index 435eaae1d..189bfc1b6 100644 --- a/rclrs/src/parameter/structured.rs +++ b/rclrs/src/parameter/structured.rs @@ -85,10 +85,10 @@ pub trait StructuredParameters: Sized { /// - [`crate::OptionalParameter`] /// - [`crate::ReadOnlyParameter`] /// -/// In these cases the [`Self`] != [`T`] and forces the usage of a generic trait. +/// In these cases [`Self`] != [`T`] and forces the usage of a generic trait. /// However, a generic trait also requires annotating this default value in the derive macro. /// For the container based structured parameters [`T`] is always [`DefaultForbidden`] -/// and therefore we can hide this form the trait + macro by using this helper trait. +/// and therefore we can hide this from the trait + macro by using this helper trait. /// The previously mentioned leaf types that actually hold parameters, are to be implemented manually anyway. /// pub trait StructuredParametersMeta: Sized { @@ -356,4 +356,30 @@ mod tests { node.declare_parameters("").unwrap(); println!("{:?}", _params); } + #[derive(Debug, StructuredParameters)] + struct AllMacroOptions { + #[param( + default = 42.0, + ignore_override, + description = "_mandatory", + constraints = "some_constraints", + discard_mismatching_prior_value, + discriminate = |av| av.default_value, + range = rclrs::ParameterRange { lower: Some(1.0), ..Default::default()}, + )] + _mandatory: rclrs::MandatoryParameter, + } + + #[test] + fn test_all_macro_options() { + let args: Vec = ["test", "--ros-args"] + .into_iter() + .map(str::to_string) + .collect(); + let context = crate::Context::new(args, rclrs::InitOptions::default()).unwrap(); + let exec = context.create_basic_executor(); + let node = exec.create_node(rclrs::NodeOptions::new("test")).unwrap(); + let _params: AllMacroOptions = node.declare_parameters("").unwrap(); + println!("{:?}", _params); + } }