Skip to content

Commit 967fe9f

Browse files
authored
Merge pull request #1313 from ekxide/iox2-1312-no-zero-copy-send-for-option-result
[#1312] no zero copy send for option result
2 parents 7135556 + 045f100 commit 967fe9f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+957
-129
lines changed

doc/release-notes/iceoryx2-unreleased.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
[#1289](https://github.com/eclipse-iceoryx/iceoryx2/issues/1289)
1919
* Add source `NodeId` to request and response header
2020
[#1308](https://github.com/eclipse-iceoryx/iceoryx2/issues/1308)
21+
* Introduce `RelocatableOption` and `RelocatableDuration` which are
22+
`ZeroCopySend`
23+
[#1312](https://github.com/eclipse-iceoryx/iceoryx2/issues/1312)
2124

2225
### Bugfixes
2326

@@ -26,6 +29,8 @@
2629
conflicts when merging.
2730
-->
2831

32+
* Remove default implementation of `ZeroCopySend` from `Option` and `Duration`
33+
[#1312](https://github.com/eclipse-iceoryx/iceoryx2/issues/1312)
2934
* Bump wheel from 0.45.1 to 0.46.3 in /iceoryx2-ffi/python
3035
[#1316](https://github.com/eclipse-iceoryx/iceoryx2/issues/1316)
3136

examples/rust/discovery_service/discovery_service_client.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ fn main() -> Result<(), Box<dyn core::error::Error>> {
2626
let node = NodeBuilder::new().create::<ipc::Service>()?;
2727

2828
let service = node
29-
.service_builder(service_name().try_into()?)
29+
.service_builder(service_name())
3030
.request_response::<(), [StaticConfig]>()
3131
.open_or_create()?;
3232

iceoryx2-bb/container/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ pub mod flatmap;
110110
/// A trait that defines the interface of a string and several string variants.
111111
pub mod string;
112112

113+
/// Implementation of an [`Option`] that has a stable memory layout and is
114+
/// shared memory compatible.
115+
pub mod relocatable_option;
116+
113117
#[doc(hidden)]
114118
pub(crate) mod vec;
115119
/// A trait that defines the interface of a vector and several vector variants.
Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
// Copyright (c) 2026 Contributors to the Eclipse Foundation
2+
//
3+
// See the NOTICE file(s) distributed with this work for additional
4+
// information regarding copyright ownership.
5+
//
6+
// This program and the accompanying materials are made available under the
7+
// terms of the Apache Software License 2.0 which is available at
8+
// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license
9+
// which is available at https://opensource.org/licenses/MIT.
10+
//
11+
// SPDX-License-Identifier: Apache-2.0 OR MIT
12+
13+
use core::hash::Hash;
14+
use core::{
15+
fmt::Debug,
16+
marker::PhantomData,
17+
ops::{Deref, DerefMut},
18+
};
19+
20+
use iceoryx2_bb_elementary_traits::{
21+
placement_default::PlacementDefault, zero_copy_send::ZeroCopySend,
22+
};
23+
use iceoryx2_log::fatal_panic;
24+
use serde::{de::Visitor, Deserialize, Serialize};
25+
26+
/// Implementation of an [`Option`] that is shared-memory compatible,
27+
/// has a stable memory layout and can be used for zero-copy cross-language
28+
/// communication.
29+
///
30+
/// The usage is as close as possible to the original [`Option`], except for
31+
/// the construction via [`Some`] and [`None`], which needs to be
32+
/// [`RelocatableOption::Some`] and [`RelocatableOption::None`].
33+
///
34+
/// # Examples
35+
///
36+
/// ## Construction Comparison
37+
///
38+
/// ```
39+
/// use iceoryx2_bb_container::relocatable_option::RelocatableOption;
40+
///
41+
/// // rust Option
42+
/// fn do_stuff_1(value: i32) -> Option<i32> {
43+
/// if value > 0 {
44+
/// Some(value)
45+
/// } else {
46+
/// None
47+
/// }
48+
/// }
49+
///
50+
/// // RelocatableOption
51+
/// fn do_stuff_2(value: i32) -> RelocatableOption<i32> {
52+
/// if value > 0 {
53+
/// RelocatableOption::Some(value)
54+
/// } else {
55+
/// RelocatableOption::None
56+
/// }
57+
/// }
58+
/// ```
59+
///
60+
/// ## Match Statements
61+
///
62+
/// ```
63+
/// use iceoryx2_bb_container::relocatable_option::RelocatableOption;
64+
///
65+
/// fn do_stuff() -> RelocatableOption<i32> {
66+
/// RelocatableOption::None
67+
/// }
68+
///
69+
/// match do_stuff() {
70+
/// RelocatableOption::Some(v) => println!("{v}"),
71+
/// RelocatableOption::None => println!("none")
72+
/// }
73+
/// ```
74+
#[repr(C, u8)]
75+
#[derive(Default, Clone, Copy, Hash, Debug, PartialEq, Eq)]
76+
pub enum RelocatableOption<T> {
77+
/// Default value, defines an [`RelocatableOption`] that does contain nothing.
78+
#[default]
79+
None,
80+
/// Defines an [`RelocatableOption`] that contains the provided type `T`.
81+
Some(T),
82+
}
83+
84+
impl<T> From<Option<T>> for RelocatableOption<T> {
85+
fn from(value: Option<T>) -> Self {
86+
match value {
87+
Some(v) => RelocatableOption::Some(v),
88+
None => RelocatableOption::None,
89+
}
90+
}
91+
}
92+
93+
impl<T> From<RelocatableOption<T>> for Option<T> {
94+
fn from(value: RelocatableOption<T>) -> Self {
95+
value.to_option()
96+
}
97+
}
98+
99+
impl<T: Serialize> Serialize for RelocatableOption<T> {
100+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
101+
where
102+
S: serde::Serializer,
103+
{
104+
match self {
105+
Self::Some(v) => serializer.serialize_some(v),
106+
Self::None => serializer.serialize_none(),
107+
}
108+
}
109+
}
110+
111+
struct RelocatableOptionVisitor<T> {
112+
_data: PhantomData<T>,
113+
}
114+
115+
impl<'de, T: Deserialize<'de>> Visitor<'de> for RelocatableOptionVisitor<T> {
116+
type Value = RelocatableOption<T>;
117+
118+
fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
119+
write!(
120+
formatter,
121+
"an optional value of type {}",
122+
core::any::type_name::<T>()
123+
)
124+
}
125+
126+
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
127+
where
128+
D: serde::Deserializer<'de>,
129+
{
130+
Ok(RelocatableOption::Some(T::deserialize(deserializer)?))
131+
}
132+
133+
fn visit_none<E>(self) -> Result<Self::Value, E>
134+
where
135+
E: serde::de::Error,
136+
{
137+
Ok(RelocatableOption::None)
138+
}
139+
}
140+
141+
impl<'de, T: Deserialize<'de>> Deserialize<'de> for RelocatableOption<T> {
142+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
143+
where
144+
D: serde::Deserializer<'de>,
145+
{
146+
deserializer.deserialize_option(RelocatableOptionVisitor { _data: PhantomData })
147+
}
148+
}
149+
150+
unsafe impl<T: ZeroCopySend> ZeroCopySend for RelocatableOption<T> {}
151+
152+
impl<T: PlacementDefault> PlacementDefault for RelocatableOption<T> {
153+
unsafe fn placement_default(ptr: *mut Self) {
154+
ptr.write(RelocatableOption::None)
155+
}
156+
}
157+
158+
impl<T> RelocatableOption<T> {
159+
/// Creates a new [`Option`] containing `T`.
160+
pub fn to_option(self) -> Option<T> {
161+
match self {
162+
Self::Some(v) => Some(v),
163+
Self::None => None,
164+
}
165+
}
166+
167+
/// Returns an [`Option`] with a reference to `T`
168+
pub fn as_option_ref(&self) -> Option<&T> {
169+
match self {
170+
Self::Some(v) => Some(v),
171+
Self::None => None,
172+
}
173+
}
174+
175+
/// Returns an [`Option`] with a mutable reference to `T`
176+
pub fn as_option_mut(&mut self) -> Option<&mut T> {
177+
match self {
178+
Self::Some(v) => Some(v),
179+
Self::None => None,
180+
}
181+
}
182+
183+
/// Converts the `RelocatableOption<T>` to `RelocatableOption<&T::Target>`.
184+
pub fn as_deref(&self) -> RelocatableOption<&<T as Deref>::Target>
185+
where
186+
T: Deref,
187+
{
188+
match self {
189+
Self::Some(v) => RelocatableOption::Some(v.deref()),
190+
Self::None => RelocatableOption::None,
191+
}
192+
}
193+
194+
/// Converts the `RelocatableOption<T>` to `RelocatableOption<&mut T::Target>`.
195+
pub fn as_deref_mut(&mut self) -> RelocatableOption<&mut <T as Deref>::Target>
196+
where
197+
T: DerefMut,
198+
{
199+
match self {
200+
Self::Some(v) => RelocatableOption::Some(v.deref_mut()),
201+
Self::None => RelocatableOption::None,
202+
}
203+
}
204+
205+
/// Returns a [`RelocatableOption`] that contains a mutable reference to `T` if
206+
/// it holds a value, otherwise it contains nothing.
207+
pub fn as_mut(&mut self) -> RelocatableOption<&mut T> {
208+
match self {
209+
Self::Some(ref mut v) => RelocatableOption::Some(v),
210+
Self::None => RelocatableOption::None,
211+
}
212+
}
213+
214+
/// Returns a [`RelocatableOption`] that contains a reference to `T` if it holds
215+
/// a value, otherwise it contains nothing.
216+
pub fn as_ref(&self) -> RelocatableOption<&T> {
217+
match self {
218+
Self::Some(ref v) => RelocatableOption::Some(v),
219+
Self::None => RelocatableOption::None,
220+
}
221+
}
222+
223+
/// Consumes the [`RelocatableOption`] and returns the contained value `T`. If
224+
/// it does not contain a value a panic is raised with the provided
225+
/// message.
226+
pub fn expect(self, msg: &str) -> T {
227+
match self {
228+
Self::Some(v) => v,
229+
Self::None => {
230+
let origin = alloc::format!(
231+
"RelocatableOption::<{}>::expect()",
232+
core::any::type_name::<T>()
233+
);
234+
fatal_panic!(from origin, "Expect: {msg}");
235+
}
236+
}
237+
}
238+
239+
/// If the [`RelocatableOption`] contains a value, the provided callback is
240+
/// called.
241+
pub fn inspect<F: FnOnce(&T)>(self, f: F) -> Self {
242+
if let Self::Some(data) = &self {
243+
f(data)
244+
}
245+
246+
self
247+
}
248+
249+
/// Returns [`true`] if the [`RelocatableOption`] does not contain a value, other
250+
/// it returns [`false`].
251+
pub fn is_none(&self) -> bool {
252+
match self {
253+
RelocatableOption::None => true,
254+
RelocatableOption::Some(_v) => false,
255+
}
256+
}
257+
258+
/// Returns [`true`] if the [`RelocatableOption`] does contain a value, other
259+
/// it returns [`false`].
260+
pub fn is_some(&self) -> bool {
261+
!self.is_none()
262+
}
263+
264+
/// Maps a `RelocatableOption<T>` to a `RelocatableOption<U>`
265+
pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> RelocatableOption<U> {
266+
match self {
267+
RelocatableOption::None => RelocatableOption::None,
268+
RelocatableOption::Some(v) => RelocatableOption::Some(f(v)),
269+
}
270+
}
271+
272+
/// Replaces the existing value of the [`RelocatableOption`] with the new value.
273+
/// The old value is returned.
274+
pub fn replace(&mut self, value: T) -> RelocatableOption<T> {
275+
core::mem::replace(self, Self::Some(value))
276+
}
277+
278+
/// Takes the value out of the [`RelocatableOption`] and returns it, leaving an
279+
/// empty [`RelocatableOption`].
280+
pub fn take(&mut self) -> RelocatableOption<T> {
281+
core::mem::take(self)
282+
}
283+
284+
/// Takes the value out of the [`RelocatableOption`] if it has a value and the
285+
/// predicate returns [`true`] leaving an empty [`RelocatableOption`].
286+
pub fn take_if<P: FnOnce(&mut T) -> bool>(&mut self, predicate: P) -> RelocatableOption<T> {
287+
match self {
288+
RelocatableOption::None => RelocatableOption::None,
289+
RelocatableOption::Some(v) => {
290+
if predicate(v) {
291+
core::mem::take(self)
292+
} else {
293+
RelocatableOption::None
294+
}
295+
}
296+
}
297+
}
298+
299+
/// Consumes the [`RelocatableOption`] and returns the value of `T`. If the
300+
/// [`RelocatableOption`] does not contain a value a panic is raised.
301+
pub fn unwrap(self) -> T {
302+
match self {
303+
Self::Some(v) => v,
304+
Self::None => {
305+
let origin = alloc::format!(
306+
"RelocatableOption::<{}>::unwrap()",
307+
core::any::type_name::<T>()
308+
);
309+
fatal_panic!(
310+
from origin,
311+
"This should never happen! Accessing the value of an empty RelocatableOption."
312+
);
313+
}
314+
}
315+
}
316+
317+
/// Consumes the [`RelocatableOption`] and either returns the contained value,
318+
/// if there is one, otherwise `default` is returned.
319+
pub fn unwrap_or(self, alternative: T) -> T {
320+
self.unwrap_or_else(|| alternative)
321+
}
322+
323+
/// Consumes the [`RelocatableOption`] and either returns the contained value,
324+
/// if there is one, otherwise `T::default()` is returned.
325+
pub fn unwrap_or_default(self) -> T
326+
where
327+
T: Default,
328+
{
329+
self.unwrap_or_else(|| T::default())
330+
}
331+
332+
/// Consumes the [`RelocatableOption`] and either returns the contained value,
333+
/// if there is one or returns the return value of the provided callback.
334+
pub fn unwrap_or_else<F: FnOnce() -> T>(self, f: F) -> T {
335+
match self {
336+
Self::Some(v) => v,
337+
Self::None => f(),
338+
}
339+
}
340+
341+
/// Consumes the [`RelocatableOption`] and returns the value of `T`.
342+
///
343+
/// # Safety
344+
///
345+
/// * [`RelocatableOption::is_some()`] == [`true`]
346+
///
347+
pub unsafe fn unwrap_unchecked(self) -> T {
348+
debug_assert!(self.is_some());
349+
match self {
350+
RelocatableOption::Some(v) => v,
351+
RelocatableOption::None => unsafe { core::hint::unreachable_unchecked() },
352+
}
353+
}
354+
}

0 commit comments

Comments
 (0)