Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Action message support #417

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
110d965
Added action template
esteve Nov 28, 2022
047c8f1
Added action generation
esteve Nov 28, 2022
556a606
Added basic create_action_client function
esteve Nov 28, 2022
dfdcbd3
dded action generation
esteve Nov 28, 2022
67c3b8b
checkin
esteve Nov 17, 2023
b197155
Fix missing exported pre_field_serde field
esteve Jan 17, 2024
13474d1
Removed extra code
esteve Jan 17, 2024
3e70087
Sketch out action server construction and destruction
nwn Jun 6, 2024
17cd980
Fix action typesupport function
nwn Jun 11, 2024
fb9b0e4
Add ActionImpl trait with internal messages and services
nwn Jul 13, 2024
5f3373f
Split srv.rs.em into idiomatic and rmw template files
nwn Jul 20, 2024
562132b
Generate underlying service definitions for actions
nwn Jul 20, 2024
dc90b21
Add runtime trait to get the UUID from a goal request
nwn Jul 20, 2024
1ed2981
Integrate RMW message methods into ActionImpl
nwn Aug 7, 2024
568bb7c
Add rosidl_runtime_rs::ActionImpl::create_feedback_message()
nwn Aug 9, 2024
23e9c94
Add GetResultService methods to ActionImpl
nwn Aug 16, 2024
6d7021a
Implement ActionImpl trait methods in generator
nwn Aug 16, 2024
7a39794
Replace set_result_response_status with create_result_response
nwn Aug 23, 2024
c5bd258
Implement client-side trait methods for action messages
nwn Sep 28, 2024
2748b5f
Format the rosidl_runtime_rs::ActionImpl trait
nwn Sep 30, 2024
507a7af
Wrap longs lines in rosidl_generator_rs action.rs
nwn Oct 1, 2024
7d30fb1
Use idiomatic message types in Action trait
nwn Oct 11, 2024
3c1663f
Cleanup ActionImpl using type aliases
mxgrey Oct 3, 2024
0493705
Formatting
nwn Oct 11, 2024
8bedfe0
Switch from std::os::raw::c_void to std::ffi::c_void
nwn Oct 12, 2024
5dece63
Clean up rosidl_generator_rs's cmake files
nwn Oct 13, 2024
db575ef
Add a short doc page on the message generation pipeline
nwn Oct 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions docs/message-generation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# `ros2_rust` Message Generation

The `ros2_rust` project strives to maintain consistency with the upstream ROS 2 message generation
system. To this end, it provides two main packages: `rosidl_generator_rs` and `rosidl_runtime_rs`.
These packages provide the infrastructure required for defining and using ROS 2 messages, services,
and actions in Rust.

At a high level, the `rosidl_generator_rs` package handles generation of interface crates from the
`.msg`, `.srv`, and `.action` files defined by a user. The `rosidl_runtime_rs` package provides
common functionality shared across message packages, such as support types and traits. Each of these
packages is described in more detail below.

## `rosidl_generator_rs`

`rosidl_generator_rs` follows a very similar pattern to the other message generation packages for
ROS 2. To tie into this pipeline, it registers itself as a `"rosidl_generate_idl_interfaces"`
extension with `ament`. Doing so ensures that message packages calling `rosidl_generate_interfaces`
will invoke the Rust language generator in addition to any others. This is accomplished using the
various CMake scripts under the `cmake` directory. When this happens, the input interface files will
be converted into IDL files which, along with additional metadata, are fed into the `generate_rs`
function of `rosidl_generator_rs/__init__.py`.

From here, the IDL files are parsed into an internal representation using the upstream
[`rosidl_parser`](https://github.com/ros2/rosidl/tree/rolling/rosidl_parser) package. This abstract
representation is then used to instantiate the various template files under the `resource`
subdirectory, producing a full Rust crate for each package.

For each input message type, two `struct`s are generated:

- An ergonomic representation of the message, using more idiomatic `std` types like `Vec` and
`String` for sequence and string fields. These are placed directly in the `msg` submodule of the
crate.
- A FFI-suitable `struct` that is ABI-compatible with the layout expected by the RMW layer. While
less ergonomic, these avoid the conversion overhead when passed to RMW. These `struct`s are placed
under the `msg::rmw` submodule.

All the produced `struct`s implement the standard traits from `std` when possible, such as `Clone`,
`PartialEq`, and `Debug`. Additionally, when the generated crate's `serde` feature is enabled, these
structs implement the `Serialize` and `Deserialize` traits.

## `rosidl_runtime_rs`

`rosidl_runtime_rs` is a runtime support package, providing `Message`, `Service`, and `Action`
traits that are implemented by the corresponding structs generated by `rosidl_generator_rs`. These
allow for generic interaction with these various interface types, both in client libraries like
`rclrs` and in user code.

The package also provides a number of string and sequence types that are ABI-compatible with their
`rosidl_runtime_c` equivalents. This allows for more ergonomic use of the RMW-native message types.
8 changes: 2 additions & 6 deletions rosidl_generator_rs/cmake/custom_command.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,10 @@

add_custom_command(
OUTPUT
${_generated_extension_files}
${_generated_common_rs_files}
${_generated_msg_rs_files}
${_generated_msg_c_files}
${_generated_srv_rs_files}
${_generated_srv_c_files}
${_generated_action_rs_files}
COMMAND ${PYTHON_EXECUTABLE} ${rosidl_generator_rs_BIN}
--generator-arguments-file "${generator_arguments_file}"
--typesupport-impls "${_typesupport_impls}"
Expand All @@ -34,11 +32,9 @@ else()
add_custom_target(
${rosidl_generate_interfaces_TARGET}${_target_suffix} ALL
DEPENDS
${_generated_extension_files}
${_generated_common_rs_files}
${_generated_msg_rs_files}
${_generated_msg_c_files}
${_generated_srv_rs_files}
${_generated_srv_c_files}
${_generated_action_rs_files}
)
endif()
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ set(_generated_common_rs_files "")

set(_generated_msg_rs_files "")
set(_generated_srv_rs_files "")
set(_generated_action_rs_files "")

set(_has_msg FALSE)
set(_has_srv FALSE)
set(_has_action FALSE)

foreach(_idl_file ${rosidl_generate_interfaces_ABS_IDL_FILES})
get_filename_component(_parent_folder "${_idl_file}" DIRECTORY)
Expand All @@ -37,13 +39,13 @@ foreach(_idl_file ${rosidl_generate_interfaces_ABS_IDL_FILES})

if(_parent_folder STREQUAL "msg")
set(_has_msg TRUE)
set(_idl_file_without_actions ${_idl_file_without_actions} ${_idl_file})
set(_idl_files ${_idl_files} ${_idl_file})
elseif(_parent_folder STREQUAL "srv")
set(_has_srv TRUE)
set(_idl_file_without_actions ${_idl_file_without_actions} ${_idl_file})
set(_idl_files ${_idl_files} ${_idl_file})
elseif(_parent_folder STREQUAL "action")
set(_has_action TRUE)
message(WARNING "Rust actions generation is not implemented")
set(_idl_files ${_idl_files} ${_idl_file})
else()
message(FATAL_ERROR "Interface file with unknown parent folder: ${_idl_file}")
endif()
Expand All @@ -67,6 +69,12 @@ if(${_has_srv})
)
endif()

if(${_has_action})
list(APPEND _generated_action_rs_files
"${_output_path}/rust/src/action.rs"
)
endif()

set(_dependency_files "")
set(_dependencies "")
foreach(_pkg_name ${rosidl_generate_interfaces_DEPENDENCY_PACKAGE_NAMES})
Expand All @@ -81,12 +89,15 @@ endforeach()
set(target_dependencies
"${rosidl_generator_rs_BIN}"
${rosidl_generator_rs_GENERATOR_FILES}
"${rosidl_generator_rs_TEMPLATE_DIR}/action.rs.em"
"${rosidl_generator_rs_TEMPLATE_DIR}/msg_idiomatic.rs.em"
"${rosidl_generator_rs_TEMPLATE_DIR}/msg_rmw.rs.em"
"${rosidl_generator_rs_TEMPLATE_DIR}/msg.rs.em"
"${rosidl_generator_rs_TEMPLATE_DIR}/srv_idiomatic.rs.em"
"${rosidl_generator_rs_TEMPLATE_DIR}/srv_rmw.rs.em"
"${rosidl_generator_rs_TEMPLATE_DIR}/srv.rs.em"
${rosidl_generate_interfaces_ABS_IDL_FILES}
${_idl_file_without_actions}
${_idl_files}
${_dependency_files})
foreach(dep ${target_dependencies})
if(NOT EXISTS "${dep}")
Expand All @@ -99,7 +110,7 @@ rosidl_write_generator_arguments(
"${generator_arguments_file}"
PACKAGE_NAME "${PROJECT_NAME}"
IDL_TUPLES "${rosidl_generate_interfaces_IDL_TUPLES}"
ROS_INTERFACE_FILES "${_idl_file_without_actions}"
ROS_INTERFACE_FILES "${_idl_files}"
ROS_INTERFACE_DEPENDENCIES "${_dependencies}"
OUTPUT_DIR "${_output_path}"
TEMPLATE_DIR "${rosidl_generator_rs_TEMPLATE_DIR}"
Expand Down Expand Up @@ -130,6 +141,7 @@ set_property(
${_generated_common_rs_files}
${_generated_msg_rs_files}
${_generated_srv_rs_files}
${_generated_action_rs_files}
PROPERTY GENERATED 1)

set(_rsext_suffix "__rsext")
Expand All @@ -144,7 +156,8 @@ endif()
if(BUILD_TESTING AND rosidl_generate_interfaces_ADD_LINTER_TESTS)
if(
NOT _generated_msg_rs_files STREQUAL "" OR
NOT _generated_srv_rs_files STREQUAL ""
NOT _generated_srv_rs_files STREQUAL "" OR
NOT _generated_action_rs_files STREQUAL ""
)
# TODO(esteve): add linters for Rust files
endif()
Expand Down
205 changes: 205 additions & 0 deletions rosidl_generator_rs/resource/action.rs.em
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
@{
from rosidl_parser.definition import (
ACTION_FEEDBACK_MESSAGE_SUFFIX,
ACTION_FEEDBACK_SUFFIX,
ACTION_GOAL_SERVICE_SUFFIX,
ACTION_GOAL_SUFFIX,
ACTION_RESULT_SERVICE_SUFFIX,
ACTION_RESULT_SUFFIX,
SERVICE_REQUEST_MESSAGE_SUFFIX,
SERVICE_RESPONSE_MESSAGE_SUFFIX,
)

action_msg_specs = []

for subfolder, action in action_specs:
action_msg_specs.append((subfolder, action.goal))
action_msg_specs.append((subfolder, action.result))
action_msg_specs.append((subfolder, action.feedback))
action_msg_specs.append((subfolder, action.feedback_message))

action_srv_specs = []

for subfolder, action in action_specs:
action_srv_specs.append((subfolder, action.send_goal_service))
action_srv_specs.append((subfolder, action.get_result_service))
}@

pub mod rmw {
@{
TEMPLATE(
'msg_rmw.rs.em',
package_name=package_name, interface_path=interface_path,
msg_specs=action_msg_specs,
get_rs_name=get_rs_name, get_rmw_rs_type=get_rmw_rs_type,
pre_field_serde=pre_field_serde,
get_idiomatic_rs_type=get_idiomatic_rs_type,
constant_value_to_rs=constant_value_to_rs)

TEMPLATE(
'srv_rmw.rs.em',
package_name=package_name, interface_path=interface_path,
srv_specs=action_srv_specs,
get_rs_name=get_rs_name, get_rmw_rs_type=get_rmw_rs_type,
pre_field_serde=pre_field_serde,
get_idiomatic_rs_type=get_idiomatic_rs_type,
constant_value_to_rs=constant_value_to_rs)
}@
} // mod rmw

@{
TEMPLATE(
'msg_idiomatic.rs.em',
package_name=package_name, interface_path=interface_path,
msg_specs=action_msg_specs,
get_rs_name=get_rs_name, get_rmw_rs_type=get_rmw_rs_type,
pre_field_serde=pre_field_serde,
get_idiomatic_rs_type=get_idiomatic_rs_type,
constant_value_to_rs=constant_value_to_rs)
}@

@{
TEMPLATE(
'srv_idiomatic.rs.em',
package_name=package_name, interface_path=interface_path,
srv_specs=action_srv_specs,
get_rs_name=get_rs_name, get_rmw_rs_type=get_rmw_rs_type,
pre_field_serde=pre_field_serde,
get_idiomatic_rs_type=get_idiomatic_rs_type,
constant_value_to_rs=constant_value_to_rs)
}@

@[for subfolder, action_spec in action_specs]

@{
type_name = action_spec.namespaced_type.name
}@

#[link(name = "@(package_name)__rosidl_typesupport_c")]
extern "C" {
fn rosidl_typesupport_c__get_action_type_support_handle__@(package_name)__@(subfolder)__@(type_name)() -> *const std::ffi::c_void;
}

// Corresponds to @(package_name)__@(subfolder)__@(type_name)
pub struct @(type_name);

impl rosidl_runtime_rs::Action for @(type_name) {
type Goal = crate::@(subfolder)::@(type_name)@(ACTION_GOAL_SUFFIX);
type Result = crate::@(subfolder)::@(type_name)@(ACTION_RESULT_SUFFIX);
type Feedback = crate::@(subfolder)::@(type_name)@(ACTION_FEEDBACK_SUFFIX);

fn get_type_support() -> *const std::ffi::c_void {
// SAFETY: No preconditions for this function.
unsafe { rosidl_typesupport_c__get_action_type_support_handle__@(package_name)__@(subfolder)__@(type_name)() }
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is a little confusing to me. Looking at (what I believe to be) the impl for this typesupport function (link)

  1. Why are we actually returning a void pointer here? This function is not marked as unsafe, but any handling of the return value may well need to use unsafe code. Could we not return a well formed type, such as rosidl_service_type_support_t?

  2. This function seems to initialize the request and response members, but I don't actually see where those members are. The @(type_name) struct appears empty. How is this working?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. You're right, we could have this return a more specific type than just c_void. However, doing so hasn't been necessary yet, and this is following the same pattern as the existing get_type_support functions for messages and services. To make this happen, we would have to create a Rust binding for the rosidl_{message,service,action}_type_support_t structs, probably in rosidl_runtime_rs. I would be inclined to leave this for a separate PR to avoid growing the scope of this one.
  2. I'm not sure what you mean here. The @(type_name) struct is empty since it's not really meant to be instantiated. It would only be used as a generic impl Action argument and to access the specific Goal, Feedback, and Result associated message types.

Copy link
Collaborator

Choose a reason for hiding this comment

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

  1. Sure, I'm fine leaving this to another PR. Just took a look at our usages (1) of this function historically and we do have as casts everywhere. as casts can be problematic. So it would be nice to eventually not need this. However, maybe there is some other reason I'm missing as to why we return void* here.

  2. I was referring to the data that function actually mutates in its implementation. I was misunderstanding and assumed that we actually held a handle to that data, but apparently we do not. It is a global variable managed by I guess the rosidl_runtime(?) that will be generated for each service.

static rosidl_service_type_support_t @(function_prefix)__@(spec.srv_name)_service_type_support_handle = {
  0,
  &@(function_prefix)__@(spec.srv_name)_service_members,
  get_service_typesupport_handle_function,
};

This is outside the scope of this PR though. Just calling attention to it because we've been bitten by global variables we directly interact with via FFI before, see #386

}
}

impl rosidl_runtime_rs::ActionImpl for @(type_name) {
type GoalStatusMessage = action_msgs::msg::rmw::GoalStatusArray;
type FeedbackMessage = crate::@(subfolder)::rmw::@(type_name)@(ACTION_FEEDBACK_MESSAGE_SUFFIX);

type SendGoalService = crate::@(subfolder)::rmw::@(type_name)@(ACTION_GOAL_SERVICE_SUFFIX);
type CancelGoalService = action_msgs::srv::rmw::CancelGoal;
type GetResultService = crate::@(subfolder)::rmw::@(type_name)@(ACTION_RESULT_SERVICE_SUFFIX);

fn create_goal_request(
goal_id: &[u8; 16],
goal: crate::@(subfolder)::rmw::@(type_name)@(ACTION_GOAL_SUFFIX),
) -> crate::@(subfolder)::rmw::@(type_name)@(ACTION_GOAL_SERVICE_SUFFIX)@(SERVICE_REQUEST_MESSAGE_SUFFIX) {
crate::@(subfolder)::rmw::@(type_name)@(ACTION_GOAL_SERVICE_SUFFIX)@(SERVICE_REQUEST_MESSAGE_SUFFIX) {
goal_id: unique_identifier_msgs::msg::rmw::UUID { uuid: *goal_id },
Copy link
Collaborator

Choose a reason for hiding this comment

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

I thought we didn't want to have a dependency on unique_identifier_msgs here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We want to avoid creating a dependency on unique_identifier_msgs from the rosidl_runtime_rs package, since the latter is a dependency of all message packages. This would cause a cyclic dependency.

However, the crates generated by the rosidl_generator_rs do have dependencies on any message packages they use as fields. In this case, unique_identifier_msgs/msg/UUID is a field of the underlying goal service request type.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ahh, I see. So the function signature of create_goal_request can't reference unique_identifier_msgs but the impl of that function actually can.

Yeah, we should explore some of the ideas discussed earlier to simplify this. Not needed for this PR though.

goal,
}
}

fn get_goal_request_uuid(
request: &crate::@(subfolder)::rmw::@(type_name)@(ACTION_GOAL_SERVICE_SUFFIX)@(SERVICE_REQUEST_MESSAGE_SUFFIX),
) -> &[u8; 16] {
&request.goal_id.uuid
}

fn create_goal_response(
accepted: bool,
stamp: (i32, u32),
) -> crate::@(subfolder)::rmw::@(type_name)@(ACTION_GOAL_SERVICE_SUFFIX)@(SERVICE_RESPONSE_MESSAGE_SUFFIX) {
crate::@(subfolder)::rmw::@(type_name)@(ACTION_GOAL_SERVICE_SUFFIX)@(SERVICE_RESPONSE_MESSAGE_SUFFIX) {
accepted,
stamp: builtin_interfaces::msg::rmw::Time {
sec: stamp.0,
nanosec: stamp.1,
},
}
}

fn get_goal_response_accepted(
response: &crate::@(subfolder)::rmw::@(type_name)@(ACTION_GOAL_SERVICE_SUFFIX)@(SERVICE_RESPONSE_MESSAGE_SUFFIX),
) -> bool {
response.accepted
}

fn get_goal_response_stamp(
response: &crate::@(subfolder)::rmw::@(type_name)@(ACTION_GOAL_SERVICE_SUFFIX)@(SERVICE_RESPONSE_MESSAGE_SUFFIX),
) -> (i32, u32) {
(response.stamp.sec, response.stamp.nanosec)
}

fn create_feedback_message(
goal_id: &[u8; 16],
feedback: crate::@(subfolder)::rmw::@(type_name)@(ACTION_FEEDBACK_SUFFIX),
) -> crate::@(subfolder)::rmw::@(type_name)@(ACTION_FEEDBACK_MESSAGE_SUFFIX) {
let mut message = crate::@(subfolder)::rmw::@(type_name)@(ACTION_FEEDBACK_MESSAGE_SUFFIX)::default();
message.goal_id.uuid = *goal_id;
message.feedback = feedback;
message
}

fn get_feedback_message_uuid(
feedback: &crate::@(subfolder)::rmw::@(type_name)@(ACTION_FEEDBACK_MESSAGE_SUFFIX),
) -> &[u8; 16] {
&feedback.goal_id.uuid
}

fn get_feedback_message_feedback(
feedback: &crate::@(subfolder)::rmw::@(type_name)@(ACTION_FEEDBACK_MESSAGE_SUFFIX),
) -> &crate::@(subfolder)::rmw::@(type_name)@(ACTION_FEEDBACK_SUFFIX) {
&feedback.feedback
}

fn create_result_request(
goal_id: &[u8; 16],
) -> crate::@(subfolder)::rmw::@(type_name)@(ACTION_RESULT_SERVICE_SUFFIX)@(SERVICE_REQUEST_MESSAGE_SUFFIX) {
crate::@(subfolder)::rmw::@(type_name)@(ACTION_RESULT_SERVICE_SUFFIX)@(SERVICE_REQUEST_MESSAGE_SUFFIX) {
goal_id: unique_identifier_msgs::msg::rmw::UUID { uuid: *goal_id },
}
}

fn get_result_request_uuid(
request: &crate::@(subfolder)::rmw::@(type_name)@(ACTION_RESULT_SERVICE_SUFFIX)@(SERVICE_REQUEST_MESSAGE_SUFFIX),
) -> &[u8; 16] {
&request.goal_id.uuid
}

fn create_result_response(
status: i8,
result: crate::@(subfolder)::rmw::@(type_name)@(ACTION_RESULT_SUFFIX),
) -> crate::@(subfolder)::rmw::@(type_name)@(ACTION_RESULT_SERVICE_SUFFIX)@(SERVICE_RESPONSE_MESSAGE_SUFFIX) {
crate::@(subfolder)::rmw::@(type_name)@(ACTION_RESULT_SERVICE_SUFFIX)@(SERVICE_RESPONSE_MESSAGE_SUFFIX) {
status,
result,
}
}

fn get_result_response_result(
response: &crate::@(subfolder)::rmw::@(type_name)@(ACTION_RESULT_SERVICE_SUFFIX)@(SERVICE_RESPONSE_MESSAGE_SUFFIX),
) -> &crate::@(subfolder)::rmw::@(type_name)@(ACTION_RESULT_SUFFIX) {
&response.result
}

fn get_result_response_status(
response: &crate::@(subfolder)::rmw::@(type_name)@(ACTION_RESULT_SERVICE_SUFFIX)@(SERVICE_RESPONSE_MESSAGE_SUFFIX),
) -> i8 {
response.status
}
}

@[end for]
4 changes: 4 additions & 0 deletions rosidl_generator_rs/resource/lib.rs.em
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ pub mod msg;
@[if len(srv_specs) > 0]@
pub mod srv;
@[end if]@

@[if len(action_specs) > 0]@
pub mod action;
@[end if]@
4 changes: 2 additions & 2 deletions rosidl_generator_rs/resource/msg_rmw.rs.em
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type_name = msg_spec.structure.namespaced_type.name

#[link(name = "@(package_name)__rosidl_typesupport_c")]
extern "C" {
fn rosidl_typesupport_c__get_message_type_support_handle__@(package_name)__@(subfolder)__@(type_name)() -> *const std::os::raw::c_void;
fn rosidl_typesupport_c__get_message_type_support_handle__@(package_name)__@(subfolder)__@(type_name)() -> *const std::ffi::c_void;
}

#[link(name = "@(package_name)__rosidl_generator_c")]
Expand Down Expand Up @@ -103,7 +103,7 @@ impl rosidl_runtime_rs::Message for @(type_name) {

impl rosidl_runtime_rs::RmwMessage for @(type_name) where Self: Sized {
const TYPE_NAME: &'static str = "@(package_name)/@(subfolder)/@(type_name)";
fn get_type_support() -> *const std::os::raw::c_void {
fn get_type_support() -> *const std::ffi::c_void {
// SAFETY: No preconditions for this function.
unsafe { rosidl_typesupport_c__get_message_type_support_handle__@(package_name)__@(subfolder)__@(type_name)() }
}
Expand Down
Loading
Loading