Conversation
|
There is currently a limitaiton where I'm using copy functions (ros2/rosidl#650) that are not yet in Foxy and Galactic. Jacob wanted to take a look at backporting those. However, I could also try to do a polyfill for them if we don't want to wait that long. |
9dcea47 to
a91ab7c
Compare
What would be involved in the creation of a "polyfill"? I'm not familiar with the process. |
I'd try implementing the functions from that PR in Rust, using |
|
@nnmm awesome work, thanks! CI seems to be failing because of a missing message, the file that's causing the problem is |
Oh, forgot to add it, fixed now. CI will still fail because of the issue mentioned above (the copy functions being Rolling-only for now). I still opened this PR for review to parallelize reviewing + work on that issue. |
I think implementing the functions in Rust for now might be good, provided the work isn't too difficult. That way, if I get pulled away to work on other projects you won't be stuck waiting on me for this to get merged/working. |
| qos: QoSProfile, | ||
| ) -> Result<Publisher<T>, RclReturnCode> | ||
| where | ||
| T: MessageDefinition<T>, |
There was a problem hiding this comment.
Why was the generic type parameter dropped?
There was a problem hiding this comment.
I just didn't see any reason why it should be there.
| @@ -0,0 +1,30 @@ | |||
| cmake_minimum_required(VERSION 3.5) | |||
There was a problem hiding this comment.
Instead of adding rclrs_example_msgs, I'd prefer using the example_interfaces or test_interface_files packages.
There was a problem hiding this comment.
The test_interface_files looks good, thanks for the tip.
There was a problem hiding this comment.
We need ros2/test_interface_files#18 to make that work, I believe
There was a problem hiding this comment.
I'd propose to merge the version with the rclrs_example_msgs if the PR I linked isn't approved by the time the rest of the review is finished, and then later remove it once it is.
There was a problem hiding this comment.
Seems like bringing in test_interface_files is more complicated than we thought. Would you be fine with leaving in rclrs_example_msgs for now?
|
@nnmm I just did a first pass. This is really awesome, you truly outdid yourself dude! I'll do a more extensive review later, but this looks great. |
jhdcs
left a comment
There was a problem hiding this comment.
Mostly a few comments/clarifications that I'd like to know about. Looks excellent, though!
| // 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 |
There was a problem hiding this comment.
Should we be pedantic, though? If ros2_rust is being used on safety-critical systems, they may want this sort of check. Or is there a better value to test against? What would the performance impact be?
There was a problem hiding this comment.
Yeah, good question. I'm not worried about performance impact, but about code complexity/overengineering.
I think ROS 2 messages will never reach this limit – certainly not on 64-bit machines, and I don't know if anyone is building ROS 2 for 32-bit systems. There is also a 1 GB message size limit, so even if we have a 32-bit system, there won't be any sequences with more than usize/2 elements. Besides, there are probably plenty of unchecked additions in rcl and rmw that would cause trouble too.
So I propose to not be pedantic about usize overflow, but to just leave comments like this one when such an issue is encountered. If anyone ever really wants to use ros2_rust in a safety-critical context, they'll need to go through the code and refactor a whole lot of stuff anyway, and then at least there's a comment to point to potential issues.
54e662c to
f26454c
Compare
jhdcs
left a comment
There was a problem hiding this comment.
Just another comment, mostly a stylistic thing. Otherwise, this looks good to me!
Thank you very much for your work!
| 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. |
There was a problem hiding this comment.
I'm not sure this comment addresses why this is safe. I feel like these safety comments should be written so that someone won't need to dive into an external C library to verify, unless they really wanted to.
Something like: "This is safe because neither self.data nor self.size are mutated, and self.data is guaranteed to be pointing to valid memory by this point, and self.size is guaranteed to be correct"?
There was a problem hiding this comment.
I rephrased it to be a little clearer. The safety argument is basically: The rosidl_runtime_c__String__copy function is implemented exactly the same way as this Clone instance: https://github.com/ros2/rosidl/blob/caef9b8f820dfcb16e6a2df9118c5669cc816642/rosidl_runtime_c/src/string_functions.c#L135-L143
We may assume that the libraries we're binding to are correct, so when we do the same thing as that library, it's safe.
I'd say that direct access to a field is the most flexible and ergonomic option, and only when that for some reason is not allowed (e.g. because it would allow violating some invariant) are getters and setters needed. This PR also has |
|
@esteve Do you know how to turn off these |
| }) | ||
| } | ||
|
|
||
| pub fn publish(&self, message: &T) -> Result<(), RclReturnCode> { |
There was a problem hiding this comment.
Re-add reference to message here
There was a problem hiding this comment.
What do you mean by "in a sense", are there any caveats?
There was a problem hiding this comment.
It's not message: &T now, it's message: impl MessageCow, which accepts T and &T (aka theConvert trait from the Matrix chat).
Suggestions for a better name welcome. In case you dislike this design, we could also split it up into two functions, publish_owned() and publish_ref().
There was a problem hiding this comment.
I don't get it, you seemed to be very keen on not having a separate create_subscription function for RMW-compatible types, but publish_owned and publish_ref are ok. Could you elaborate?
Let's keep it as is now, though, and we could refine the APIs later if needed, though it'll be more painful.
There was a problem hiding this comment.
Sure. The distinction between publish_owned() and publish_ref() is of course orthogonal to the question of which message type is published. But you're right that both are a "have two functions for doing the thing vs. having one more general function" question.
In this case here, I'm more on the fence between splitting vs unifying, since the more general function comes at the cost of a more complicated signature: The MessageCow trait. In the case of the extra create_subscription function, the create_subscription function would look the same whether or not we also have a create_rmw_subscription, so there's no extra complexity in the unified version. Being simpler is not the only argument in favor of one subscription function, but I'll keep that discussion in the other thread.
| @@ -109,7 +110,7 @@ impl Node { | |||
| callback: F, | |||
There was a problem hiding this comment.
Given that the RmwMessage API is specific to ros2-rust, I prefer if there are two separate functions here. One for the expected normal ROS2 API (i.e. idiomatic), and another (e.g. called create_subscription_rmw_type) for RmwMessage. That way it's more explicit and the latter can be marked as an optimization for Rust.
There was a problem hiding this comment.
Why would we have a redundant function like this? The current Subscription API can receive Messages, and both idiomatic and RMW-compatible messages are instances of that. Adding a second API that only supports the latter type is redundant and confuses only, imo. Uniformity is nice.
There was a problem hiding this comment.
Precisely for uniformity with the other bindings, no other bindings accept these so-call rmw types, all of them support idiomatic types only. Adding a second API clears that confusion by explicitly signaling that it's specific to ros2-rust. Rust, very much like Python, and unlike C++, encourages explicitness vs implicitness. Explicitness is nice too.
There was a problem hiding this comment.
I don't want the more efficient message type to become a second-class citizen by only being accessible through less-than-canonical APIs. There shouldn't be any confusion with the two message types, since (1) I tried to document it really well and (2) even if someone skips the documentation, they will not be confused, they simply won't know that there's also the RMW-compatible message type. For such a user, the API will arguably appear more uniform with other languages than if there was an create_subscription_rmw_type function they need to look up.
Besides, there is also lots of stuff that is specific to rclcpp, and none of it is marked as such (topic statistics, generic pub/sub, intraprocess, etc.).
There was a problem hiding this comment.
through less-than-canonical APIs
The RMW-compatible message API is already non-canonical, because it does not have any counterpart in the other bindings. APIs are not only about names, but also semantics, the arguments they accept and the values they return. Having a distinctly different name emphasizes that it behaves differently from the other bindings, and that users should expect differences.
Besides, there is also lots of stuff that is specific to rclcpp, and none of it is marked as such (topic statistics, generic pub/sub, intraprocess, etc.).
Because the goal is that eventually that functionality might be incorporated into other bindings. For example, rclc could potentially get support for intraprocess.
Anyway, I rather not waste more time on this, but it's important to get APIs right early on, because changing them later is far more difficult
There was a problem hiding this comment.
As for counterparts in other bindings, here's my perspective for posterity:
- Python is slow anyway, and usability of a raw C message (= our "RMW-compatible" message) would be absymal from a Python perspective, so of course the messages in that language are an idiomatic version that are converted to/from the C message.
- C and C++ have the privilege of being natively RMW-compatible, so they can be both idiomatic and maximally efficient. They are the counterpart to the RMW-compatible and the idiomatic messages.
- I assume other language bindings also use their own idiomatic message types that are converted to/from C messages. However, I believe none of the other languages are quite as performance-focused as Rust/C/C++.
Hence, Rust is kind of unique, in that its users expect maximum performance but it can't achieve that (yet) by serializing/deserializing its standard library types to/from CDR directly. So you could argue that it would be canonical to use RMW-compatible messages because that's what the other performance-focused language bindings do. Or you could argue that it would be canonical to use idiomatic messages because that's what all language bindings do.
I think the solution in this PR, to offer both, isn't too bad. And I placed the idiomatic messages in the <pkg>::msg namespace and the RMW-compatible ones in <pkg>::msg::rmw, because they can't be both in the same namespace, but I believe we shouldn't hide the RMW-compatible messages more than that if it's not technically necessary.
I don't think the time spent discussing on this is wasted. Hope I'm not too stubborn! If you insist, I'll add the separate function. Or I can also poll the ROS + Rust people I know to get more input. But since you approved, I take it that it's okay to merge as it is now and potentially discuss it again later.
There was a problem hiding this comment.
- I assume other language bindings also use their own idiomatic message types that are converted to/from C messages. However, I believe none of the other languages are quite as performance-focused as Rust/C/C++.
Ada reuses the C data layout in memory, so there is no overhead involved. The price is somewhat un-idiomatic use when sequences are involved that I hope to alleviate at some point in the future.
I'll look into it, neither of those tests should run on Rust files and there are no more C files to be checked. |
|
Thanks for fixing the linter @esteve. Now this PR is ready to merge from my side: I added the workaround for the |
Thanks, there is still some unanswered feedback, could you address it? CI is not passing either, do you know why? |
4c0f6ae to
3aecf1f
Compare
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
This requires a cfg for conditionally compiling the code depending on the ROS distro. I'm setting that cfg in the build script instead of colcon-ros-cargo so that building with cargo doesn't become too annoying.
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
MessagetraitOn
rosidl_runtime_rs: This package is the successor ofrclrs_msg_utilitiespackage, but doesn't have much in common with it anymore.It provides common types and functionality for messages. The
StringandSequencetypes and their variants in that package essentially wrap C types from therosidl_runtime_cpackage 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
Eq,OrdandHashwhen a message contains no floatsHow to review
I know that this is a really large PR. If desired, I can split it up for reviewing. Here are some steps I suggest to get familiar with the changes:
message_demoand read the codecargo docs for therosidl_runtime_rscrate and read themmsg.rs.emand__init__.pyfiles