<!DOCTYPE HTML>
<html lang="en" class="light" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>User Defined Data Structure - safe_drive: Formally Specified Rust Bindings for ROS2</title>
<!-- Custom HTML head -->
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="favicon.svg">
<link rel="shortcut icon" href="favicon.png">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/general.css">
<link rel="stylesheet" href="css/chrome.css">
<link rel="stylesheet" href="css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="highlight.css">
<link rel="stylesheet" href="tomorrow-night.css">
<link rel="stylesheet" href="ayu-highlight.css">
<!-- Custom theme stylesheets -->
</head>
<body class="sidebar-visible no-js">
<div id="body-container">
<!-- Provide site root to javascript -->
<script>
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('light')
html.classList.add(theme);
var body = document.querySelector('body');
body.classList.remove('no-js')
body.classList.add('js');
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var body = document.querySelector('body');
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
body.classList.remove('sidebar-visible');
body.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded "><a href="index.html"><strong aria-hidden="true">1.</strong> Top</a></li><li class="chapter-item expanded "><a href="tutorial.html"><strong aria-hidden="true">2.</strong> Tutorial</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="setup.html"><strong aria-hidden="true">2.1.</strong> Setting-up</a></li><li class="chapter-item expanded "><a href="pubsub.html"><strong aria-hidden="true">2.2.</strong> Publish and Subscribe</a></li><li class="chapter-item expanded "><a href="timer.html"><strong aria-hidden="true">2.3.</strong> Timer</a></li><li class="chapter-item expanded "><a href="multi_pubsub.html"><strong aria-hidden="true">2.4.</strong> Multi-threaded Publish and Subscribe</a></li><li class="chapter-item expanded "><a href="message.html" class="active"><strong aria-hidden="true">2.5.</strong> User Defined Data Structure</a></li><li class="chapter-item expanded "><a href="service.html"><strong aria-hidden="true">2.6.</strong> Service</a></li><li class="chapter-item expanded "><a href="parameter.html"><strong aria-hidden="true">2.7.</strong> Parameter</a></li><li class="chapter-item expanded "><a href="zerocopy.html"><strong aria-hidden="true">2.8.</strong> Zero Copy Publish and Subscribe</a></li><li class="chapter-item expanded "><a href="allocator.html"><strong aria-hidden="true">2.9.</strong> Custom Memory Allocator</a></li><li class="chapter-item expanded "><a href="pusbsub_and_service.html"><strong aria-hidden="true">2.10.</strong> Request to Server in Callback</a></li></ol></li><li class="chapter-item expanded "><a href="contribution.html"><strong aria-hidden="true">3.</strong> Contribution Guide</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="setup_contribution.html"><strong aria-hidden="true">3.1.</strong> Setting-up for Contribution</a></li><li class="chapter-item expanded "><a href="build_and_test.html"><strong aria-hidden="true">3.2.</strong> Build and Test</a></li><li class="chapter-item expanded "><a href="editor_setup.html"><strong aria-hidden="true">3.3.</strong> Editor Setting-up</a></li><li class="chapter-item expanded "><a href="internal_msgs.html"><strong aria-hidden="true">3.4.</strong> Generating Messages Included by safe_drive</a></li><li class="chapter-item expanded "><a href="c_apis.html"><strong aria-hidden="true">3.5.</strong> C APIs</a></li><li class="chapter-item expanded "><a href="document.html"><strong aria-hidden="true">3.6.</strong> Documentation</a></li></ol></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<!-- Track and set sidebar scroll position -->
<script>
var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
sidebarScrollbox.addEventListener('click', function(e) {
if (e.target.tagName === 'A') {
sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
}
}, { passive: true });
var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
sessionStorage.removeItem('sidebar-scroll');
if (sidebarScrollTop) {
// preserve sidebar scroll position when navigating via links within sidebar
sidebarScrollbox.scrollTop = sidebarScrollTop;
} else {
// scroll sidebar to current active section when navigating via "next/previous chapter" buttons
var activeSection = document.querySelector('#sidebar .active');
if (activeSection) {
activeSection.scrollIntoView({ block: 'center' });
}
}
</script>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">safe_drive: Formally Specified Rust Bindings for ROS2</h1>
<div class="right-buttons">
<a href="print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="user-defined-data-structure"><a class="header" href="#user-defined-data-structure">User Defined Data Structure</a></h1>
<p><a href="https://github.com/tier4/safe_drive_tutorial/tree/main/msgtest">Source code</a>.</p>
<p>Until previous tutorial, we used pre-defined message types.
In this tutorial, we will describe how to define user defined types.</p>
<h2 id="create-project-directory"><a class="header" href="#create-project-directory">Create Project Directory</a></h2>
<p>Then create a project directory as follows.</p>
<pre><code class="language-text">$ mkdir -p msgtest/src
</code></pre>
<p>Throughout this tutorial, we will create 4 packages as follows.</p>
<div class="table-wrapper"><table><thead><tr><th>packages</th><th>description</th></tr></thead><tbody>
<tr><td>msgtest/src/my_interfaces</td><td>defining types of ROS2</td></tr>
<tr><td>msgtest/src/talker</td><td>a publisher</td></tr>
<tr><td>msgtest/src/listener</td><td>a subscriber</td></tr>
</tbody></table>
</div>
<h2 id="workspaces-cargotoml"><a class="header" href="#workspaces-cargotoml">Workspace's <code>Cargo.toml</code></a></h2>
<p>The workspace's <code>Cargo.toml</code> should be created as follows.</p>
<p><code>msgtest/src/Cargo.toml</code></p>
<pre><code class="language-toml">[workspace]
members = ["talker", "listener"]
</code></pre>
<p>Then, create projects as follows.</p>
<pre><code class="language-text">$ cd msgtest/src
$ cargo new talker
$ cargo new listener
</code></pre>
<h2 id="define-user-defined-type"><a class="header" href="#define-user-defined-type">Define User Defined Type</a></h2>
<p>To define message types, we have to create a ROS2's package,
and create a '.msg' files.
The package can be created in the ordinary way of ROS2 as follows.</p>
<pre><code class="language-text">$ cd msgtest/src
$ ros2 pkg create --build-type ament_cmake my_interfaces
$ cd my_interfaces
$ mkdir msg
$ cd msg
</code></pre>
<h3 id="primitive-type-my_interfacesmsgmymsgmsg"><a class="header" href="#primitive-type-my_interfacesmsgmymsgmsg">Primitive Type: <code>my_interfaces/msg/MyMsg.msg</code></a></h3>
<p>Then create a file, <code>msgtest/src/my_interfaces/msg/MyMsg.msg</code>, as follows.</p>
<pre><code class="language-text">int32 integer_value
int32[] unbounded_integer_array
int32[5] five_integers_array
int32[<=5] up_to_five_integers_array
</code></pre>
<p>There are 4 values in this type.</p>
<ul>
<li><code>integer_value</code> : a value of the <code>int32</code> type</li>
<li><code>unbounded_integer_array</code> : an unbounded array of the <code>int32</code> type</li>
<li><code>five_integers_array</code> : an array which size is 5 of the <code>int32</code> type</li>
<li><code>up_to_five_integers_array</code> : an array whose size is up to 5 of the <code>int32</code> type</li>
</ul>
<h3 id="using-user-defiend-type-my_interfacesmsgmymsgsmsg"><a class="header" href="#using-user-defiend-type-my_interfacesmsgmymsgsmsg">Using User Defiend Type: <code>my_interfaces/msg/MyMsgs.msg</code></a></h3>
<p>We can use the <code>MyMsg</code> previously defined in another message type, <code>msgtest/src/my_interfaces/msg/MyMsgs.msg</code>, as follows.</p>
<pre><code class="language-text">MyMsg msg1
MyMsg msg2
</code></pre>
<h3 id="string-type-my_interfacesmsgmymsgstrmsg"><a class="header" href="#string-type-my_interfacesmsgmymsgstrmsg">String Type: <code>my_interfaces/msg/MyMsgStr.msg</code></a></h3>
<p>A size of an array can be specified as described above,
a length of a string can be also specified.
For example, <code>string<=10</code> is a type of string,
but its length is up to 10.</p>
<p>We prepare <code>msgtest/src/my_interfaces/msg/MyMsgStr.msg</code> as follows.</p>
<pre><code class="language-text">string message
string[2] static_array_str
string[] dynamic_array_str
string[<=3] bounded_array_str
string<=10 bounded_str
string<=10[2] static_array_bounded_str
string<=10[] dynamic_array_bounded_str
string<=10[<=3] bounded_array_bounded_str
</code></pre>
<h3 id="edit-my_interfacescmakeliststxt"><a class="header" href="#edit-my_interfacescmakeliststxt">Edit <code>my_interfaces/CMakeLists.txt</code></a></h3>
<p>To generate C or C++ files and libraries for used defined types,
we have to edit <code>CMakeLists.txt</code> as follows.</p>
<p><code>msgtest/src/my_interfaces/CMakeLists.txt</code></p>
<pre><code class="language-cmake">find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/MyMsg.msg"
"msg/MyMsgs.msg"
"msg/MyMsgStr.msg"
)
</code></pre>
<p>We have to specify messages files in <code>CMakeLists.txt</code>.</p>
<h3 id="edit-my_interfacespackagexml"><a class="header" href="#edit-my_interfacespackagexml">Edit <code>my_interfaces/package.xml</code></a></h3>
<p>We also have to edit <code>package.xml</code> as follows.</p>
<p><code>msgtest/src/my_interfaces/package.xml</code></p>
<pre><code class="language-xml"><build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
</code></pre>
<h2 id="user-defined-type-in-rust"><a class="header" href="#user-defined-type-in-rust">User Defined Type in Rust</a></h2>
<p>Primitive types are translated into Rust's types as follows.</p>
<div class="table-wrapper"><table><thead><tr><th>ROS2</th><th>Rust</th></tr></thead><tbody>
<tr><td>bool</td><td>bool</td></tr>
<tr><td>byte</td><td>u8</td></tr>
<tr><td>char</td><td>i8</td></tr>
<tr><td>int8</td><td>i8</td></tr>
<tr><td>uint8</td><td>u8</td></tr>
<tr><td>int16</td><td>i16</td></tr>
<tr><td>uint16</td><td>u16</td></tr>
<tr><td>int32</td><td>i32</td></tr>
<tr><td>uint32</td><td>u32</td></tr>
<tr><td>int64</td><td>i64</td></tr>
<tr><td>uint64</td><td>u64</td></tr>
<tr><td>float32</td><td>f32</td></tr>
<tr><td>float64</td><td>f64</td></tr>
<tr><td>string</td><td>safe_drive::msg::RosString</td></tr>
</tbody></table>
</div>
<h3 id="generated-types"><a class="header" href="#generated-types">Generated Types</a></h3>
<p>Array types are generated as follows.</p>
<div class="table-wrapper"><table><thead><tr><th>ROS2</th><th>Rust</th></tr></thead><tbody>
<tr><td>int32[5]</td><td>[i32; 5]</td></tr>
<tr><td>int32[]</td><td>safe_drive::msg::I32Seq<0></td></tr>
<tr><td>int32[<=5]</td><td>safe_drive::msg::I32Seq<5></td></tr>
</tbody></table>
</div>
<p><code>0</code> of <code>I32Seq<0></code> indicates unbounded, and <code>5</code> of <code>I32Seq<5></code> indicates less than or equal to 5.
So, <code>MyMsg</code> and <code>MyMsgs</code> are generated as follows.</p>
<h2 id="talker"><a class="header" href="#talker">Talker</a></h2>
<p>Let's implement a talker which publishes <code>MyMsgs</code> periodically.</p>
<h3 id="edit-talkercargotoml"><a class="header" href="#edit-talkercargotoml">Edit <code>talker/Cargo.toml</code></a></h3>
<p>To use the generated types in Rust, we have to edit <code>Cargo.toml</code> as follows.
The most important thing is to add <code>my_interfaces</code>, which defines message types we use.</p>
<pre><code class="language-toml"># msgtest/src/talker/Cargo.toml
[dependencies]
safe_drive = "0.4"
my_interfaces = { path = "/tmp/safe_drive_tutorial/msgtest/my_interfaces" }
[package.metadata.ros]
msg = ["my_interfaces"]
msg_dir = "/tmp/safe_drive_tutorial/msgtest"
safe_drive_version = "0.3"
</code></pre>
<h3 id="create-talkerpackagexml"><a class="header" href="#create-talkerpackagexml">Create <code>talker/package.xml</code></a></h3>
<p>Then create <code>package.xml</code> as follows.</p>
<p><code>msgtest/src/talker/package.xml</code></p>
<pre><code class="language-xml"><?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>talker</name>
<version>0.0.0</version>
<description>Talker in Rust</description>
<maintainer email="yuuki.takano@tier4.jp">Yuuki Takano</maintainer>
<license>Apache License 2.0</license>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<depend>my_interfaces</depend>
<export>
<build_type>ament_cargo</build_type>
</export>
</package>
</code></pre>
<p>Don't forget <code><depend>my_interfaces</depend></code>.</p>
<h3 id="generated-files"><a class="header" href="#generated-files">Generated Files</a></h3>
<p>When you run <code>colcon</code>, it generate <code>my_interfaces</code> in Rust.</p>
<pre><code class="language-text">$ cd msgtest
$ colcon build --cargo-args --release
</code></pre>
<p>Then, you can find Rust's files as follows.</p>
<p><code>/tmp/safe_drive_tutorial/msgtest/my_interfaces/src/msg/my_msg.rs</code></p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>#[repr(C)]
#[derive(Debug)]
pub struct MyMsg {
pub integer_value: i32,
pub unbounded_integer_array: safe_drive::msg::I32Seq<0>,
pub five_integers_array: [i32; 5],
pub up_to_five_integers_array: safe_drive::msg::I32Seq<5>,
}
<span class="boring">}</span></code></pre></pre>
<p><code>/tmp/safe_drive_tutorial/msgtest/my_interfaces/src/msg/my_msgs.rs</code></p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>#[repr(C)]
#[derive(Debug)]
pub struct MyMsgs {
pub msg1: crate::msg::my_msg::MyMsg,
pub msg2: crate::msg::my_msg::MyMsg,
}
<span class="boring">}</span></code></pre></pre>
<h3 id="edit-talkersrcmainrs"><a class="header" href="#edit-talkersrcmainrs">Edit <code>talker/src/main.rs</code></a></h3>
<p>If you want to know how to implement a subscriber or a publisher, please see <a href="./pubsub.html">a tutorial of Pub/Sub</a>. This section describes how to handle a messages generated by <code>ros2msg_to_rs</code>.</p>
<p><code>msgtest/src/talker/src/main.rs</code></p>
<pre><pre class="playground"><code class="language-rust">use safe_drive::{
context::Context,
error::DynError,
logger::Logger,
msg::{I32Seq, RosStringSeq},
pr_info,
};
use std::time::Duration;
fn main() -> Result<(), DynError> {
// Create a context.
let ctx = Context::new()?;
// Create a node.
let node = ctx.create_node("talker", None, Default::default())?;
// Create a publisher.
let publisher = node.create_publisher::<my_interfaces::msg::MyMsgs>("my_topic", None)?;
// Create a logger.
let logger = Logger::new("talker");
// Create a message
let my_msg1 = create_message()?;
let my_msg2 = create_message()?;
let mut my_msgs = my_interfaces::msg::MyMsgs::new().ok_or("failed to create MyMsgs")?;
my_msgs.msg1 = my_msg1;
my_msgs.msg2 = my_msg2;
loop {
pr_info!(logger, "send: {:?}", my_msgs); // Print log.
// Send a message.
publisher.send(&my_msgs)?;
std::thread::sleep(Duration::from_secs(1));
}
}
fn create_message() -> Result<my_interfaces::msg::MyMsg, DynError> {
let mut my_msg = my_interfaces::msg::MyMsg::new().unwrap();
my_msg.integer_value = 10;
// int32[5] five_integers_array
my_msg.five_integers_array[0] = 11;
my_msg.five_integers_array[1] = 13;
my_msg.five_integers_array[2] = 49;
my_msg.five_integers_array[3] = 55;
my_msg.five_integers_array[4] = 19;
// int32[] unbounded_integer_array
let mut msgs = I32Seq::new(3).unwrap();
let ref_msgs = msgs.as_slice_mut();
ref_msgs[0] = 6;
ref_msgs[1] = 7;
ref_msgs[2] = 8;
my_msg.unbounded_integer_array = msgs;
// int32[<=5] up_to_five_integers_array
let mut msgs = I32Seq::new(2).unwrap();
let ref_msgs = msgs.as_slice_mut();
ref_msgs[0] = 2;
ref_msgs[1] = 3;
my_msg.up_to_five_integers_array = msgs;
Ok(my_msg)
}</code></pre></pre>
<p>Primitive types and arrays can be handles in the ordinary way of Rust as follows.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>my_msg.integer_value = 10;
// int32[5] five_integers_array
my_msg.five_integers_array[0] = 11;
my_msg.five_integers_array[1] = 13;
my_msg.five_integers_array[2] = 49;
my_msg.five_integers_array[3] = 55;
my_msg.five_integers_array[4] = 19;
<span class="boring">}</span></code></pre></pre>
<p>To access elements of unbounded or bounded arrays,
we can use <code>as_slice_mut()</code> or <code>as_slice()</code> methods as follows.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// unbounded or unbounded array
let mut msgs = I32Seq::new(3).unwrap();
let ref_msgs = msgs.as_slice_mut();
<span class="boring">}</span></code></pre></pre>
<p><code>as_slice_mut()</code> returns a mutable slice,
you can thus update the elements of the array via the slice.</p>
<h2 id="listener"><a class="header" href="#listener">Listener</a></h2>
<p>Let's then implement a listener which receive messages published by the talker.</p>
<h3 id="edit-listenercargotoml"><a class="header" href="#edit-listenercargotoml">Edit <code>listener/Cargo.toml</code></a></h3>
<p>The listener also requires <code>my_interfaces</code>, and edit <code>Cargo.toml</code> as follows.</p>
<pre><code class="language-toml"># msgtest/src/listener/Cargo.toml
[dependencies]
safe_drive = "0.4"
my_interfaces = { path = "/tmp/safe_drive_tutorial/msgtest/my_interfaces" }
[package.metadata.ros]
msg = ["my_interfaces"]
msg_dir = "/tmp/safe_drive_tutorial/msgtest"
safe_drive_version = "0.3"
</code></pre>
<h3 id="create-listenerpackagexml"><a class="header" href="#create-listenerpackagexml">Create <code>listener/package.xml</code></a></h3>
<p>Then create <code>package.xml</code> as follows.</p>
<p><code>msgtest/src/listener/package.xml</code></p>
<pre><code class="language-xml"><?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>listener</name>
<version>0.0.0</version>
<description>Listener in Rust</description>
<maintainer email="yuuki.takano@tier4.jp">Yuuki Takano</maintainer>
<license>Apache License 2.0</license>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<depend>my_interfaces</depend>
<export>
<build_type>ament_cargo</build_type>
</export>
</package>
</code></pre>
<p>Don't forget <code><depend>my_interfaces</depend></code>.</p>
<h3 id="edit-listenersrcmainrs"><a class="header" href="#edit-listenersrcmainrs">Edit <code>listener/src/main.rs</code></a></h3>
<p>The listener can also be implemented straightforwardly as follows.</p>
<p><code>msgtest/src/listener/src/main.rs</code></p>
<pre><pre class="playground"><code class="language-rust">use my_interfaces;
use safe_drive::{context::Context, error::DynError, logger::Logger, pr_info};
fn main() -> Result<(), DynError> {
// Create a context.
let ctx = Context::new()?;
// Create a node.
let node = ctx.create_node("listener", None, Default::default())?;
// Create a subscriber.
let subscriber = node.create_subscriber::<my_interfaces::msg::MyMsgs>("my_topic", None)?;
// Create a logger.
let logger = Logger::new("listener");
// Create a selector.
let mut selector = ctx.create_selector()?;
pr_info!(logger, "listening");
// Add a callback function.
selector.add_subscriber(
subscriber,
Box::new(move |msg| {
let msg = &msg.msg1;
pr_info!(logger, "message: {}", msg.integer_value);
for msg in msg.five_integers_array.iter() {
pr_info!(logger, "five_integers_array: {}", msg);
}
for msg in msg.unbounded_integer_array.iter() {
pr_info!(logger, "unbounded_integer_array: {}", msg);
}
for msg in msg.up_to_five_integers_array.iter() {
pr_info!(logger, "up_to_five_integers_array: {}", msg);
}
}),
);
// Spin.
loop {
selector.wait()?;
}
}</code></pre></pre>
<h2 id="compilation-and-execution"><a class="header" href="#compilation-and-execution">Compilation and Execution</a></h2>
<p>Now, we can compile and execute the talker and listener. Let's do it!</p>
<h3 id="compile"><a class="header" href="#compile">Compile</a></h3>
<p>The compilation can be performed by using <code>colcon</code> as follows.</p>
<pre><code class="language-text">$ cd msgtest
$ colcon build --cargo-args --release
$ . ./install/setup.bash
</code></pre>
<h3 id="execute-listener"><a class="header" href="#execute-listener">Execute Listener</a></h3>
<p>The listener can be executed by using <code>ros2</code> as follows.
After executing the talker, it receives messages as follows.</p>
<pre><code class="language-text">$ ros2 run listener listener
[INFO] [1658305910.013449534] [listener]: listening
[INFO] [1658305914.359791460] [listener]: message: 10
[INFO] [1658305914.359839382] [listener]: five_integers_array: 11
[INFO] [1658305914.359867532] [listener]: five_integers_array: 13
[INFO] [1658305914.359880763] [listener]: five_integers_array: 49
[INFO] [1658305914.359889731] [listener]: five_integers_array: 55
[INFO] [1658305914.359900913] [listener]: five_integers_array: 19
[INFO] [1658305914.359912534] [listener]: unbounded_integer_array: 6
[INFO] [1658305914.359924084] [listener]: unbounded_integer_array: 7
[INFO] [1658305914.359936971] [listener]: unbounded_integer_array: 8
[INFO] [1658305914.359946479] [listener]: up_to_five_integers_array: 2
[INFO] [1658305914.359959422] [listener]: up_to_five_integers_array: 3
</code></pre>
<h3 id="execute-talker"><a class="header" href="#execute-talker">Execute Talker</a></h3>
<p>To execute the talker, open a new terminal window and execute it as follows.</p>
<pre><code class="language-text">$ cd msgtest
$ . ./install/setup.bash
$ ros2 run talker talker
[INFO] [1658305913.359250753] [talker]: send: MyMsgs { msg1: MyMsg { integer_value: 10, unbounded_integer_array: I32Seq(rosidl_runtime_c__int32__Sequence { data: 0x55a0653f7aa0, size: 3, capacity: 3 }), five_integers_array: [11, 13, 49, 55, 19], up_to_five_integers_array: I32Seq(rosidl_runtime_c__int32__Sequence { data: 0x55a0653efaa0, size: 2, capacity: 2 }) }, msg2: MyMsg { integer_value: 10, unbounded_integer_array: I32Seq(rosidl_runtime_c__int32__Sequence { data: 0x55a0653f7e30, size: 3, capacity: 3 }), five_integers_array: [11, 13, 49, 55, 19], up_to_five_integers_array: I32Seq(rosidl_runtime_c__int32__Sequence { data: 0x55a0653f7e50, size: 2, capacity: 2 }) }
</code></pre>
<p>Nicely done! Now, we can define new types and handle the types in Rust.</p>
<h2 id="string-type"><a class="header" href="#string-type">String Type</a></h2>
<p>Arrays of string are bit different from arrays of primitive types.
<code>string</code> is unbounded string, and <code>string<=5</code> is bounded string whose length is up to 5.
So, there are arrays for unbounded and bounded strings as follows; <code>msg</code> is <code>safe_drive::msg</code>.</p>
<div class="table-wrapper"><table><thead><tr><th>ROS</th><th>Rust</th></tr></thead><tbody>
<tr><td>string</td><td>msg::RosString<0></td></tr>
<tr><td>string[]</td><td>msg::StringSeq<0, 0></td></tr>
<tr><td>string[<=5]</td><td>msg::StringSeq<0, 5></td></tr>
<tr><td>string[10]</td><td>[msg::RosString<0>; 10]</td></tr>
<tr><td>string<=5</td><td>msg::RosString<5></td></tr>
<tr><td>string<=5[<=10]</td><td>msg::StringSeq<5, 10></td></tr>
<tr><td>string<=5[10]</td><td>[msg::RosString<5>; 10]</td></tr>
</tbody></table>
</div>
<p><code>RosString<0></code> is a type of unbounded string, and <code>RosString<5></code> is a type of bounded string whose length is less than or equal to 5.
<code>5</code> of <code>StringSeq<5, 10></code> indicates the length of a string is less than or equal to 5,
and <code>10</code> of it indicates the length of the array is less than or equal to 10.</p>
<p>For example, the following ROS2 message type can be translated into the <code>MyMsgStr</code> as follows.</p>
<pre><code class="language-text">string message
string[2] static_array_str
string[] dynamic_array_str
string[<=3] bounded_array_str
string<=10 bounded_str
string<=10[2] static_array_bounded_str
string<=10[] dynamic_array_bounded_str
string<=10[<=3] bounded_array_bounded_str
</code></pre>
<p><code>/tmp/safe_drive_tutorial/msgtest/my_interfaces/src/msg/my_msg_str.rs</code></p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>#[repr(C)]
#[derive(Debug)]
pub struct MyMsgStr {
pub message: safe_drive::msg::RosString<0>,
pub static_array_str: [safe_drive::msg::RosString<0>; 2],
pub dynamic_array_str: safe_drive::msg::RosStringSeq<0, 0>,
pub bounded_array_str: safe_drive::msg::RosStringSeq<0, 3>,
pub bounded_str: safe_drive::msg::RosString<10>,
pub static_array_bounded_str: [safe_drive::msg::RosString<10>; 2],
pub dynamic_array_bounded_str: safe_drive::msg::RosStringSeq<10, 0>,
pub bounded_array_bounded_str: safe_drive::msg::RosStringSeq<10, 3>,
}
<span class="boring">}</span></code></pre></pre>
<p>To access to elements of string arrays,
we can use <code>as_slice_mut()</code> or <code>as_slice_mut()</code> as follows.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>fn _create_message_str() -> Result<my_interfaces::msg::MyMsgStr, DynError> {
let mut my_msg = my_interfaces::msg::MyMsgStr::new().unwrap();
// string message
my_msg.message.assign("Hello, World!");
// string[2] static_array_str
my_msg.static_array_str[0].assign("static array 0");
my_msg.static_array_str[1].assign("static array 1");
// string[] dynamic_array_str
let mut msgs = RosStringSeq::new(3).ok_or("failed to create string")?;
let ref_msgs = msgs.as_slice_mut();
ref_msgs[0].assign("dynamic array 0");
ref_msgs[1].assign("dynamic array 1");
ref_msgs[2].assign("dynamic array 2");
my_msg.dynamic_array_str = msgs;
// string[<=3] bounded_array_str
let mut msgs = RosStringSeq::new(2).ok_or("failed to create string")?;
let ref_msgs = msgs.as_slice_mut();
ref_msgs[0].assign("bounded array 0");
ref_msgs[1].assign("bounded array 1");
my_msg.bounded_array_str = msgs;
// string<=10 bounded_str
my_msg.bounded_str.assign("Hello!");
// string<=10[2] static_array_bounded_str
my_msg.static_array_bounded_str[0].assign("msg1");
my_msg.static_array_bounded_str[1].assign("msg2");
// string<=10[] dynamic_array_bounded_str
let mut msgs = RosStringSeq::new(3).ok_or("failed to create string")?;
let ref_msgs = msgs.as_slice_mut();
ref_msgs[0].assign("msg3");
ref_msgs[1].assign("msg4");
ref_msgs[2].assign("msg5");
my_msg.dynamic_array_bounded_str = msgs;
// string<=10[<=3] bounded_array_bounded_str
let mut msgs = RosStringSeq::new(2).ok_or("failed to create string")?;
let ref_msgs = msgs.as_slice_mut();
ref_msgs[0].assign("msg3");
ref_msgs[1].assign("msg5");
my_msg.bounded_array_bounded_str = msgs;
Ok(my_msg)
}
<span class="boring">}</span></code></pre></pre>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="multi_pubsub.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="service.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="multi_pubsub.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="service.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script>
window.playground_copyable = true;
</script>
<script src="elasticlunr.min.js"></script>
<script src="mark.min.js"></script>
<script src="searcher.js"></script>
<script src="clipboard.min.js"></script>
<script src="highlight.js"></script>
<script src="book.js"></script>
<!-- Custom JS scripts -->
<script src="mermaid.min.js"></script>
<script src="mermaid-init.js"></script>
</div>
</body>
</html>