feat(parameters): support common Rust types as parameters - #691
Open
azerupi wants to merge 1 commit into
Open
Conversation
A parameter could only be declared with the Rust type that most directly represents one of the nine ROS 2 parameter types, so a list of names had to be handled as Arc<[Arc<str>]> and a port number as i64 even where the application would otherwise use Vec<String> and u16. Add ParameterVariant for String, PathBuf, Vec<String>, Vec<i64>, Vec<f64>, Vec<bool>, Vec<u8>, f32 and the integer types that fit in an i64 (i8, i16, i32, u8, u16, u32). Conversions back from a stored value can be partial -- not every i64 is a u16 -- which the type check on the write paths rejects before it can be stored, so such a parameter can never hold a value of the wrong type. Every scalar type also has a Vec form, using whichever ROS 2 array type holds the scalar's representation, so a Vec<u16> is an integer array whose elements are each checked against the range of a u16. Reading an element goes through the scalar type's own conversion, so what an element may be is decided in one place, and a rejection says which element was at fault. Vec<u8> is the one exception: a sequence of bytes is a ROS 2 byte array rather than an integer array. Ranges are expressed in the parameter's own type, so range = 1024..=49151 on a u16 parameter means what it appears to and a bound the type cannot hold is a compile error. Each of these types also names the value just below an exclusive end, the previous integer or the previous representable f32, so range = 1024..49152 admits up to 49151. The limits a type imposes are carried by its ParameterConversion and narrow whatever the declaration asks for, so a bound left open is filled in and the result reaches the descriptor's IntegerRange where `ros2 param describe` and rqt_reconfigure can read it, rather than only being discovered by having a value rejected. Duration is not among them, and needs no wrapper type to be one. A declaration says which unit it is stored in through its own conversion, for instance ParameterConversion::double(Duration::as_secs_f64, Duration::try_from_secs_f64), which puts the unit at the declaration where it belongs rather than in a type that exists only to carry it. u64, usize, i128 and u128 are deliberately unsupported: every way of storing a value above i64::MAX in a ROS 2 parameter silently produces a number the application did not ask for. Declaring one now says so, along with what the parameter types are and how to use a type of your own, through a diagnostic::on_unimplemented on ParameterVariant, rather than reporting a bare unsatisfied trait bound. Assisted-by: Claude:claude-opus-5 [Claude Code]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A parameter could only be declared with the Rust type that most directly represents one of the nine ROS 2 parameter types. A list of names had to be
Arc<[Arc<str>]>and a port number ani64, even where the application would naturally useVec<String>andu16.Solution
Implement
ParameterVariantfor the Rust types an application would reach for anyway, so the declaration says what the value is and the narrowing that implies is enforced and reported.Types added
String,PathBufi8,i16,i32,u8,u16,u32f32Vec<T>for every scalar above, plusVec<i64>,Vec<f64>,Vec<bool>,Vec<u8>Vec<u8>is the one exception to array mapping: a sequence of bytes is a ROS 2 byte array, not an integer array.u64,usize,i128andu128are deliberately unsupported because a parameter value is stored as ani64Narrowing is enforced
A conversion back from a stored value can be partial, not every
i64is au16. The type check added in the previous PR rejects those on both write paths, so a narrow parameter can never hold a value that goes out of bounds of the Rust type, including overSetParameters:Arrays are checked element-wise and the rejection says which element was at fault:
Reading an element goes through the scalar type's own conversion, so what a
u16may be is decided in exactly one place.Narrowing
A
u16is bounded by what it can hold, so the limits ride on the type'sParameterConversionand combine with ranges:rqt_reconfigureand anything else reading the descriptor now sees the real bound, instead of an operator discovering it by having a value rejected. Constraints replace (the declaration's text wins), ranges narrow (a value must satisfy both).