Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
5 changes: 5 additions & 0 deletions doc/release-notes/iceoryx2-unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
[#1289](https://github.com/eclipse-iceoryx/iceoryx2/issues/1289)
* Add source `NodeId` to request and response header
[#1308](https://github.com/eclipse-iceoryx/iceoryx2/issues/1308)
* Introduce `RelocatableOption` and `RelocatableDuration` which are
`ZeroCopySend`
[#1312](https://github.com/eclipse-iceoryx/iceoryx2/issues/1312)

### Bugfixes

Expand All @@ -26,6 +29,8 @@
conflicts when merging.
-->

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ fn main() -> Result<(), Box<dyn core::error::Error>> {
let node = NodeBuilder::new().create::<ipc::Service>()?;

let service = node
.service_builder(service_name().try_into()?)
.service_builder(service_name())
.request_response::<(), [StaticConfig]>()
.open_or_create()?;

Expand Down
4 changes: 4 additions & 0 deletions iceoryx2-bb/container/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ pub mod flatmap;
/// A trait that defines the interface of a string and several string variants.
pub mod string;

/// Implementation of an [`Option`] that has a stable memory layout and is
/// shared memory compatible.
pub mod relocatable_option;

#[doc(hidden)]
pub(crate) mod vec;
/// A trait that defines the interface of a vector and several vector variants.
Expand Down
354 changes: 354 additions & 0 deletions iceoryx2-bb/container/src/relocatable_option.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,354 @@
// Copyright (c) 2026 Contributors to the Eclipse Foundation
//
// See the NOTICE file(s) distributed with this work for additional
// information regarding copyright ownership.
//
// This program and the accompanying materials are made available under the
// terms of the Apache Software License 2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license
// which is available at https://opensource.org/licenses/MIT.
//
// SPDX-License-Identifier: Apache-2.0 OR MIT

use core::hash::Hash;
use core::{
fmt::Debug,
marker::PhantomData,
ops::{Deref, DerefMut},
};

use iceoryx2_bb_elementary_traits::{
placement_default::PlacementDefault, zero_copy_send::ZeroCopySend,
};
use iceoryx2_log::fatal_panic;
use serde::{de::Visitor, Deserialize, Serialize};

/// Implementation of an [`Option`] that is shared-memory compatible,
/// has a stable memory layout and can be used for zero-copy cross-language
/// communication.
///
/// The usage is as close as possible to the original [`Option`], except for
/// the construction via [`Some`] and [`None`], which needs to be
/// [`RelocatableOption::Some`] and [`RelocatableOption::None`].
///
/// # Examples
///
/// ## Construction Comparison
///
/// ```
/// use iceoryx2_bb_container::relocatable_option::RelocatableOption;
///
/// // rust Option
/// fn do_stuff_1(value: i32) -> Option<i32> {
/// if value > 0 {
/// Some(value)
/// } else {
/// None
/// }
/// }
///
/// // RelocatableOption
/// fn do_stuff_2(value: i32) -> RelocatableOption<i32> {
/// if value > 0 {
/// RelocatableOption::Some(value)
/// } else {
/// RelocatableOption::None
/// }
/// }
/// ```
///
/// ## Match Statements
///
/// ```
/// use iceoryx2_bb_container::relocatable_option::RelocatableOption;
///
/// fn do_stuff() -> RelocatableOption<i32> {
/// RelocatableOption::None
/// }
///
/// match do_stuff() {
/// RelocatableOption::Some(v) => println!("{v}"),
/// RelocatableOption::None => println!("none")
/// }
/// ```
#[repr(C, u8)]
#[derive(Default, Clone, Copy, Hash, Debug, PartialEq, Eq)]
pub enum RelocatableOption<T> {
/// Default value, defines an [`RelocatableOption`] that does contain nothing.
#[default]
None,
/// Defines an [`RelocatableOption`] that contains the provided type `T`.
Some(T),
}

impl<T> From<Option<T>> for RelocatableOption<T> {
fn from(value: Option<T>) -> Self {
match value {
Some(v) => RelocatableOption::Some(v),
None => RelocatableOption::None,
}
}
}

impl<T> From<RelocatableOption<T>> for Option<T> {
fn from(value: RelocatableOption<T>) -> Self {
value.to_option()
}
}

impl<T: Serialize> Serialize for RelocatableOption<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
Self::Some(v) => serializer.serialize_some(v),
Self::None => serializer.serialize_none(),
}
}
}

struct RelocatableOptionVisitor<T> {
_data: PhantomData<T>,
}

impl<'de, T: Deserialize<'de>> Visitor<'de> for RelocatableOptionVisitor<T> {
type Value = RelocatableOption<T>;

fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(
formatter,
"an optional value of type {}",
core::any::type_name::<T>()
)
}

fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(RelocatableOption::Some(T::deserialize(deserializer)?))
}

fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(RelocatableOption::None)
}
}

impl<'de, T: Deserialize<'de>> Deserialize<'de> for RelocatableOption<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_option(RelocatableOptionVisitor { _data: PhantomData })
}
}

unsafe impl<T: ZeroCopySend> ZeroCopySend for RelocatableOption<T> {}

impl<T: PlacementDefault> PlacementDefault for RelocatableOption<T> {
unsafe fn placement_default(ptr: *mut Self) {
ptr.write(RelocatableOption::None)
}
}

impl<T> RelocatableOption<T> {
/// Creates a new [`Option`] containing `T`.
pub fn to_option(self) -> Option<T> {
match self {
Self::Some(v) => Some(v),
Self::None => None,
}
}

/// Returns an [`Option`] with a reference to `T`
pub fn as_option_ref(&self) -> Option<&T> {
match self {
Self::Some(v) => Some(v),
Self::None => None,
}
}

/// Returns an [`Option`] with a mutable reference to `T`
pub fn as_option_mut(&mut self) -> Option<&mut T> {
match self {
Self::Some(v) => Some(v),
Self::None => None,
}
}

/// Converts the `RelocatableOption<T>` to `RelocatableOption<&T::Target>`.
pub fn as_deref(&self) -> RelocatableOption<&<T as Deref>::Target>
where
T: Deref,
{
match self {
Self::Some(v) => RelocatableOption::Some(v.deref()),
Self::None => RelocatableOption::None,
}
}

/// Converts the `RelocatableOption<T>` to `RelocatableOption<&mut T::Target>`.
pub fn as_deref_mut(&mut self) -> RelocatableOption<&mut <T as Deref>::Target>
where
T: DerefMut,
{
match self {
Self::Some(v) => RelocatableOption::Some(v.deref_mut()),
Self::None => RelocatableOption::None,
}
}

/// Returns a [`RelocatableOption`] that contains a mutable reference to `T` if
/// it holds a value, otherwise it contains nothing.
pub fn as_mut(&mut self) -> RelocatableOption<&mut T> {
match self {
Self::Some(ref mut v) => RelocatableOption::Some(v),
Self::None => RelocatableOption::None,
}
}

/// Returns a [`RelocatableOption`] that contains a reference to `T` if it holds
/// a value, otherwise it contains nothing.
pub fn as_ref(&self) -> RelocatableOption<&T> {
match self {
Self::Some(ref v) => RelocatableOption::Some(v),
Self::None => RelocatableOption::None,
}
}

/// Consumes the [`RelocatableOption`] and returns the contained value `T`. If
/// it does not contain a value a panic is raised with the provided
/// message.
pub fn expect(self, msg: &str) -> T {
match self {
Self::Some(v) => v,
Self::None => {
let origin = alloc::format!(
"RelocatableOption::<{}>::expect()",
core::any::type_name::<T>()
);
fatal_panic!(from origin, "Expect: {msg}");
}
}
}

/// If the [`RelocatableOption`] contains a value, the provided callback is
/// called.
pub fn inspect<F: FnOnce(&T)>(self, f: F) -> Self {
if let Self::Some(data) = &self {
f(data)
}

self
}

/// Returns [`true`] if the [`RelocatableOption`] does not contain a value, other
/// it returns [`false`].
pub fn is_none(&self) -> bool {
match self {
RelocatableOption::None => true,
RelocatableOption::Some(_v) => false,
}
}

/// Returns [`true`] if the [`RelocatableOption`] does contain a value, other
/// it returns [`false`].
pub fn is_some(&self) -> bool {
!self.is_none()
}

/// Maps a `RelocatableOption<T>` to a `RelocatableOption<U>`
pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> RelocatableOption<U> {
match self {
RelocatableOption::None => RelocatableOption::None,
RelocatableOption::Some(v) => RelocatableOption::Some(f(v)),
}
}

/// Replaces the existing value of the [`RelocatableOption`] with the new value.
/// The old value is returned.
pub fn replace(&mut self, value: T) -> RelocatableOption<T> {
core::mem::replace(self, Self::Some(value))
}

/// Takes the value out of the [`RelocatableOption`] and returns it, leaving an
/// empty [`RelocatableOption`].
pub fn take(&mut self) -> RelocatableOption<T> {
core::mem::take(self)
}

/// Takes the value out of the [`RelocatableOption`] if it has a value and the
/// predicate returns [`true`] leaving an empty [`RelocatableOption`].
pub fn take_if<P: FnOnce(&mut T) -> bool>(&mut self, predicate: P) -> RelocatableOption<T> {
match self {
RelocatableOption::None => RelocatableOption::None,
RelocatableOption::Some(v) => {
if predicate(v) {
core::mem::take(self)
} else {
RelocatableOption::None
}
}
}
}

/// Consumes the [`RelocatableOption`] and returns the value of `T`. If the
/// [`RelocatableOption`] does not contain a value a panic is raised.
pub fn unwrap(self) -> T {
match self {
Self::Some(v) => v,
Self::None => {
let origin = alloc::format!(
"RelocatableOption::<{}>::unwrap()",
core::any::type_name::<T>()
);
fatal_panic!(
from origin,
"This should never happen! Accessing the value of an empty RelocatableOption."
);
}
}
}

/// Consumes the [`RelocatableOption`] and either returns the contained value,
/// if there is one, otherwise `default` is returned.
pub fn unwrap_or(self, alternative: T) -> T {
self.unwrap_or_else(|| alternative)
}

/// Consumes the [`RelocatableOption`] and either returns the contained value,
/// if there is one, otherwise `T::default()` is returned.
pub fn unwrap_or_default(self) -> T
where
T: Default,
{
self.unwrap_or_else(|| T::default())
}

/// Consumes the [`RelocatableOption`] and either returns the contained value,
/// if there is one or returns the return value of the provided callback.
pub fn unwrap_or_else<F: FnOnce() -> T>(self, f: F) -> T {
match self {
Self::Some(v) => v,
Self::None => f(),
}
}

/// Consumes the [`RelocatableOption`] and returns the value of `T`.
///
/// # Safety
///
/// * [`RelocatableOption::is_some()`] == [`true`]
///
pub unsafe fn unwrap_unchecked(self) -> T {
debug_assert!(self.is_some());
match self {
RelocatableOption::Some(v) => v,
RelocatableOption::None => unsafe { core::hint::unreachable_unchecked() },
}
}
}
Loading
Loading