Skip to content

Commit 772b4e2

Browse files
author
Adrian Willenbücher
committed
ABI-compatible Option and Result
1 parent 62cb1a9 commit 772b4e2

File tree

3 files changed

+609
-0
lines changed

3 files changed

+609
-0
lines changed

src/containers/inline/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@
1111
// SPDX-License-Identifier: Apache-2.0
1212
// *******************************************************************************
1313

14+
mod option;
1415
mod queue;
16+
mod result;
1517
mod string;
1618
mod vec;
1719

20+
pub use self::option::InlineOption;
1821
pub use self::queue::InlineQueue;
22+
pub use self::result::InlineResult;
1923
pub use self::string::InlineString;
2024
pub use self::vec::InlineVec;

src/containers/inline/option.rs

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
// *******************************************************************************
2+
// Copyright (c) 2025 Contributors to the Eclipse Foundation
3+
//
4+
// See the NOTICE file(s) distributed with this work for additional
5+
// information regarding copyright ownership.
6+
//
7+
// This program and the accompanying materials are made available under the
8+
// terms of the Apache License Version 2.0 which is available at
9+
// https://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
// *******************************************************************************
13+
14+
use core::cmp;
15+
use core::fmt;
16+
use core::mem::MaybeUninit;
17+
18+
/// An optional value, similar to [`Option`] in the Rust standard library.
19+
///
20+
/// This data structure has a stable, well-defined memory layout and satisfies the requirements for
21+
/// [ABI-compatible types](https://eclipse-score.github.io/score/main/features/communication/abi_compatible_data_types/index.html).
22+
/// Unlike the built-in [`Option`] or other enum-based types, this data structure will **not** be affected by
23+
/// [*null-pointer optimization*](https://doc.rust-lang.org/core/option/index.html#representation),
24+
/// where invalid byte representations of `T` are used to encode the `None` case.
25+
///
26+
/// **Note:** When encoding an instance of this type in other programming languages,
27+
/// the `is_some` field must only be assigned the binary values `0x00` and `0x01`;
28+
/// everything else results in *Undefined Behavior*.
29+
#[derive(Clone, Copy)]
30+
#[repr(C)]
31+
pub struct InlineOption<T: Copy> {
32+
/// This is valid/initialized if and only if `is_some` is true.
33+
value: MaybeUninit<T>,
34+
is_some: bool,
35+
}
36+
37+
impl<T: Copy> InlineOption<T> {
38+
/// Creates a new instance populated by `value`.
39+
pub const fn some(value: T) -> Self {
40+
Self {
41+
value: MaybeUninit::new(value),
42+
is_some: true,
43+
}
44+
}
45+
46+
/// Creates an empty instance.
47+
pub const fn none() -> Self {
48+
Self {
49+
value: MaybeUninit::uninit(),
50+
is_some: false,
51+
}
52+
}
53+
54+
/// Creates a new instance from a standard [`Option`].
55+
pub fn from_option(option: Option<T>) -> Self {
56+
if let Some(value) = option {
57+
Self::some(value)
58+
} else {
59+
Self::none()
60+
}
61+
}
62+
63+
/// Converts this instance into a standard [`Option`].
64+
pub const fn into_option(self) -> Option<T> {
65+
if self.is_some {
66+
// SAFETY: `is_some` is true, so `value` must be valid as per its invariant.
67+
Some(unsafe { self.value.assume_init() })
68+
} else {
69+
None
70+
}
71+
}
72+
73+
/// Returns an optional reference to the contained value.
74+
pub const fn as_ref(&self) -> Option<&T> {
75+
if self.is_some {
76+
// SAFETY: `is_some` is true, so `value` must be valid as per its invariant.
77+
Some(unsafe { self.value.assume_init_ref() })
78+
} else {
79+
None
80+
}
81+
}
82+
83+
/// Returns an optional mutable reference to the contained value.
84+
pub const fn as_mut(&mut self) -> Option<&mut T> {
85+
if self.is_some {
86+
// SAFETY: `is_some` is true, so `value` must be valid as per its invariant.
87+
Some(unsafe { self.value.assume_init_mut() })
88+
} else {
89+
None
90+
}
91+
}
92+
93+
/// Returns `true` if and only if this instance contains a value.
94+
pub const fn is_some(&self) -> bool {
95+
self.is_some
96+
}
97+
98+
/// Returns `true` if and only if this instance is empty.
99+
pub const fn is_none(&self) -> bool {
100+
!self.is_some
101+
}
102+
}
103+
104+
impl<T: Copy> Default for InlineOption<T> {
105+
fn default() -> Self {
106+
Self {
107+
value: MaybeUninit::uninit(),
108+
is_some: false,
109+
}
110+
}
111+
}
112+
113+
impl<T: Copy> From<T> for InlineOption<T> {
114+
fn from(value: T) -> Self {
115+
Self {
116+
value: MaybeUninit::new(value),
117+
is_some: true,
118+
}
119+
}
120+
}
121+
122+
impl<T: Copy> From<Option<T>> for InlineOption<T> {
123+
fn from(option: Option<T>) -> Self {
124+
Self::from_option(option)
125+
}
126+
}
127+
128+
impl<T: Copy> From<InlineOption<T>> for Option<T> {
129+
fn from(option: InlineOption<T>) -> Self {
130+
option.into_option()
131+
}
132+
}
133+
134+
impl<T: PartialEq + Copy> PartialEq for InlineOption<T> {
135+
fn eq(&self, other: &Self) -> bool {
136+
match (self.as_ref(), other.as_ref()) {
137+
(Some(a), Some(b)) => a.eq(b),
138+
(Some(_), None) => false,
139+
(None, Some(_)) => false,
140+
(None, None) => true,
141+
}
142+
}
143+
}
144+
145+
impl<T: Eq + Copy> Eq for InlineOption<T> {}
146+
147+
impl<T: PartialOrd + Copy> PartialOrd for InlineOption<T> {
148+
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
149+
match (self.as_ref(), other.as_ref()) {
150+
(Some(a), Some(b)) => a.partial_cmp(b),
151+
(Some(_), None) => Some(cmp::Ordering::Greater),
152+
(None, Some(_)) => Some(cmp::Ordering::Less),
153+
(None, None) => Some(cmp::Ordering::Equal),
154+
}
155+
}
156+
}
157+
158+
impl<T: Ord + Copy> Ord for InlineOption<T> {
159+
fn cmp(&self, other: &Self) -> cmp::Ordering {
160+
match (self.as_ref(), other.as_ref()) {
161+
(Some(a), Some(b)) => a.cmp(b),
162+
(Some(_), None) => cmp::Ordering::Greater,
163+
(None, Some(_)) => cmp::Ordering::Less,
164+
(None, None) => cmp::Ordering::Equal,
165+
}
166+
}
167+
}
168+
169+
impl<T: Copy> fmt::Display for InlineOption<T>
170+
where
171+
for<'a> Option<&'a T>: fmt::Display,
172+
{
173+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174+
fmt::Display::fmt(&self.as_ref(), f)
175+
}
176+
}
177+
178+
impl<T: Copy> fmt::Debug for InlineOption<T>
179+
where
180+
for<'a> Option<&'a T>: fmt::Debug,
181+
{
182+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183+
fmt::Debug::fmt(&self.as_ref(), f)
184+
}
185+
}
186+
187+
#[cfg(test)]
188+
mod tests {
189+
use super::*;
190+
191+
#[test]
192+
fn from_and_into() {
193+
let original_some = Some(0x1234567890abcdef_u64);
194+
let inline_some = InlineOption::from_option(original_some);
195+
assert!(inline_some.is_some());
196+
assert!(!inline_some.is_none());
197+
assert_eq!(inline_some.into_option(), original_some);
198+
199+
let original_none: Option<u64> = None;
200+
let inline_none = InlineOption::from_option(original_none);
201+
assert!(!inline_none.is_some());
202+
assert!(inline_none.is_none());
203+
assert_eq!(inline_none.into_option(), original_none);
204+
}
205+
206+
#[test]
207+
fn as_ref() {
208+
let original_some = Some(0x1234567890abcdef_u64);
209+
let inline_some = InlineOption::from_option(original_some);
210+
assert_eq!(inline_some.as_ref(), original_some.as_ref());
211+
212+
let original_none: Option<u64> = None;
213+
let inline_none = InlineOption::from_option(original_none);
214+
assert_eq!(inline_none.as_ref(), original_none.as_ref());
215+
}
216+
217+
#[test]
218+
fn as_mut() {
219+
let mut original_some = Some(0x1234567890abcdef_u64);
220+
let mut inline_some = InlineOption::from_option(original_some);
221+
assert_eq!(inline_some.as_mut(), original_some.as_mut());
222+
*original_some.as_mut().unwrap() = 5;
223+
*inline_some.as_mut().unwrap() = 5;
224+
assert_eq!(inline_some.as_mut(), original_some.as_mut());
225+
226+
let mut original_none: Option<u64> = None;
227+
let mut inline_none = InlineOption::from_option(original_none);
228+
assert_eq!(inline_none.as_mut(), original_none.as_mut());
229+
}
230+
231+
#[test]
232+
fn eq() {
233+
#[track_caller]
234+
fn check(lhs: &InlineOption<i32>, rhs: &InlineOption<i32>) {
235+
// Check that InlineOption behaves exactly like Option, since Option's implementation is assumed to be correct.
236+
assert_eq!(lhs.eq(rhs), lhs.as_ref().eq(&rhs.as_ref()));
237+
assert_eq!(lhs.ne(rhs), lhs.as_ref().ne(&rhs.as_ref()));
238+
}
239+
240+
let some_5 = InlineOption::some(5);
241+
let some_6 = InlineOption::some(6);
242+
let none = InlineOption::none();
243+
// Check all combinations
244+
for lhs in &[some_5, some_6, none] {
245+
for rhs in &[some_5, some_6, none] {
246+
check(lhs, rhs);
247+
}
248+
}
249+
}
250+
251+
#[test]
252+
fn partial_cmp() {
253+
#[track_caller]
254+
fn check(lhs: &InlineOption<i32>, rhs: &InlineOption<i32>) {
255+
// Check that InlineOption behaves exactly like Option, since Option's implementation is assumed to be correct.
256+
assert_eq!(lhs.partial_cmp(rhs), lhs.as_ref().partial_cmp(&rhs.as_ref()));
257+
assert_eq!(lhs < rhs, lhs.as_ref() < rhs.as_ref());
258+
assert_eq!(lhs <= rhs, lhs.as_ref() <= rhs.as_ref());
259+
assert_eq!(lhs > rhs, lhs.as_ref() > rhs.as_ref());
260+
assert_eq!(lhs >= rhs, lhs.as_ref() >= rhs.as_ref());
261+
}
262+
263+
let some_5 = InlineOption::some(5);
264+
let some_6 = InlineOption::some(6);
265+
let none = InlineOption::none();
266+
// Check all combinations
267+
for lhs in &[some_5, some_6, none] {
268+
for rhs in &[some_5, some_6, none] {
269+
check(lhs, rhs);
270+
}
271+
}
272+
}
273+
274+
#[test]
275+
fn cmp() {
276+
#[track_caller]
277+
fn check(lhs: &InlineOption<i32>, rhs: &InlineOption<i32>) {
278+
// Check that InlineOption behaves exactly like Option, since Option's implementation is assumed to be correct.
279+
assert_eq!(lhs.cmp(rhs), lhs.as_ref().cmp(&rhs.as_ref()));
280+
assert_eq!(lhs.max(rhs).as_ref(), lhs.as_ref().max(rhs.as_ref()));
281+
assert_eq!(lhs.min(rhs).as_ref(), lhs.as_ref().min(rhs.as_ref()));
282+
}
283+
284+
let some_5 = InlineOption::some(5);
285+
let some_6 = InlineOption::some(6);
286+
let none = InlineOption::none();
287+
// Check all combinations
288+
for lhs in &[some_5, some_6, none] {
289+
for rhs in &[some_5, some_6, none] {
290+
check(lhs, rhs);
291+
}
292+
}
293+
}
294+
}

0 commit comments

Comments
 (0)