Skip to main content

tvm_ffi/
optional.rs

1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements.  See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership.  The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License.  You may obtain a copy of the License at
9 *
10 *   http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied.  See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19//! In-place mirror of C++ `ffi::Optional<T>` for non-object-pointer `T`
20//! (`include/tvm/ffi/optional.h`).
21//!
22//! C++ `ffi::Optional<T>` is backed by one 16-byte `TVMFFIAny` for scalar,
23//! string, and other non-object-pointer values, with
24//! `type_index == kTVMFFINone` meaning `nullopt`. [`Optional<T>`] mirrors that
25//! representation.
26//!
27//! ObjectRef, ObjectPtr, and Arc cases are intentionally excluded. Their C++
28//! optional is one nullable object pointer, which Rust's niche-optimized
29//! [`std::option::Option<X>`] already mirrors. Using `Optional<X>` for an object
30//! class is rejected at compile time so the two ABI layouts cannot be confused.
31//!
32//! `Optional<T>` is `#[repr(transparent)]` over [`Any`] and decodes the cell in
33//! place. It is named `Optional` (not `Option`) to match the C++ type while
34//! keeping the pointer-backed object case visually distinct.
35
36use crate::any::Any;
37use crate::string::{Bytes, String};
38use crate::type_traits::AnyCompatible;
39use std::fmt::{self, Debug};
40use std::marker::PhantomData;
41use tvm_ffi_sys::TVMFFITypeIndex as TypeIndex;
42
43/// Marker for values whose C++ `ffi::Optional<T>` uses the 16-byte
44/// `TVMFFIAny` representation.
45///
46/// Object classes deliberately do not implement this trait. Use `Option<X>`
47/// for those values; it is the pointer-sized mirror of C++'s nullable
48/// `ObjectPtr`-backed optional.
49///
50/// ```compile_fail,E0277
51/// use tvm_ffi::{Array, Optional};
52/// let _ = Optional::<Array<i64>>::none();
53/// ```
54#[diagnostic::on_unimplemented(
55    message = "`Optional<{Self}>` only mirrors non-object-pointer `ffi::Optional` values",
56    label = "`{Self}` uses the object-pointer optional representation",
57    note = "use `Option<{Self}>` for object classes; it is the compatible nullable-pointer layout"
58)]
59pub unsafe trait OptionalCompatible: AnyCompatible {}
60
61macro_rules! impl_optional_compatible {
62    ($($t:ty),* $(,)?) => {
63        $(unsafe impl OptionalCompatible for $t {})*
64    };
65}
66
67impl_optional_compatible!(
68    bool,
69    i8,
70    i16,
71    i32,
72    i64,
73    isize,
74    u8,
75    u16,
76    u32,
77    u64,
78    usize,
79    f32,
80    f64,
81    *mut core::ffi::c_void,
82    crate::DLDataType,
83    crate::DLDevice,
84    String,
85    Bytes,
86);
87
88unsafe impl<T: OptionalCompatible> OptionalCompatible for Option<T> {}
89
90/// In-place mirror of C++ `ffi::Optional<T>`: a single 16-byte `TVMFFIAny` cell
91/// (wrapped as [`Any`]) whose `type_index == kTVMFFINone` encodes `nullopt`.
92///
93/// Layout-compatible with the C++ type (`size_of == 16`); see the [module
94/// docs](self). Reuses [`Any`]'s reference-counting `Clone`/`Drop`, which are a
95/// no-op on the `nullopt` cell (`type_index` below `kTVMFFIStaticObjectBegin`).
96#[repr(transparent)]
97pub struct Optional<T: OptionalCompatible> {
98    // Holds either the value's `TVMFFIAny` representation or a `kTVMFFINone` cell.
99    data: Any,
100    _marker: PhantomData<T>,
101}
102
103// Must stay 16 bytes / `TVMFFIAny`-aligned to overlay a C++ `ffi::Optional<T>`
104// field in place, independent of `T`.
105const _: () = assert!(
106    std::mem::size_of::<Optional<i64>>() == 16
107        && std::mem::size_of::<Optional<String>>() == 16
108        && std::mem::align_of::<Optional<i64>>() == std::mem::align_of::<crate::TVMFFIAny>()
109);
110
111impl<T: OptionalCompatible> Optional<T> {
112    /// An engaged optional holding `value`.
113    #[inline]
114    pub fn some(value: T) -> Self {
115        Self {
116            data: Any::from(value),
117            _marker: PhantomData,
118        }
119    }
120
121    /// A disengaged optional (`nullopt`, a `kTVMFFINone` cell).
122    #[inline]
123    pub fn none() -> Self {
124        Self {
125            data: Any::new(),
126            _marker: PhantomData,
127        }
128    }
129
130    /// Whether a value is present.
131    #[inline]
132    pub fn has_value(&self) -> bool {
133        self.data.type_index() != TypeIndex::kTVMFFINone as i32
134    }
135
136    /// Whether the optional is `nullopt`.
137    #[inline]
138    pub fn is_none(&self) -> bool {
139        !self.has_value()
140    }
141
142    /// Decodes the value in place, cloning it out (ref-counted `inc_ref` for
143    /// object payloads). Returns `None` when `nullopt`. No FFI call.
144    #[inline]
145    pub fn get(&self) -> Option<T> {
146        self.data.try_as::<T>()
147    }
148
149    /// Takes the value out, consuming self (moves the payload, no `inc_ref`).
150    #[inline]
151    pub fn into_option(self) -> Option<T> {
152        if self.has_value() {
153            // Move the value out of the owning `Any` without dropping it, then
154            // transfer ownership of the payload into `T` (no inc/dec ref).
155            let mut raw = unsafe { Any::into_raw_ffi_any(self.data) };
156            Some(unsafe { T::move_from_any_after_check(&mut raw) })
157        } else {
158            None
159        }
160    }
161
162    /// Overwrites the value in place, dropping the previous one first (dec-ref'd
163    /// if it was an object payload).
164    #[inline]
165    pub fn set(&mut self, value: Option<T>) {
166        // Assignment drops the old `Any` (dec_ref if object) before storing the new.
167        self.data = match value {
168            Some(v) => Any::from(v),
169            None => Any::new(),
170        };
171    }
172}
173
174impl Optional<String> {
175    /// Borrows the engaged string as `&str`, or `None` when `nullopt`.
176    ///
177    /// Reinterprets the in-cell string without cloning: an engaged cell holds a
178    /// `String`'s exact 16-byte representation, so `&Any` can be viewed as
179    /// `&String`.
180    #[inline]
181    pub fn as_str(&self) -> Option<&str> {
182        if self.has_value() {
183            // SAFETY: an engaged `Optional<String>` cell is byte-identical to a
184            // `String` (both are `#[repr(transparent)]` over the 16-byte cell),
185            // and the borrow is tied to `&self`.
186            let s = unsafe { &*(&self.data as *const Any as *const String) };
187            Some(s.as_str())
188        } else {
189            None
190        }
191    }
192}
193
194impl<T: OptionalCompatible> Default for Optional<T> {
195    /// `nullopt`, matching the C++ default constructor.
196    #[inline]
197    fn default() -> Self {
198        Self::none()
199    }
200}
201
202impl<T: OptionalCompatible> Clone for Optional<T> {
203    #[inline]
204    fn clone(&self) -> Self {
205        Self {
206            // `Any::clone` inc_refs an object payload; a `nullopt` cell is a no-op.
207            data: self.data.clone(),
208            _marker: PhantomData,
209        }
210    }
211}
212
213impl<T: OptionalCompatible + PartialEq> PartialEq for Optional<T> {
214    #[inline]
215    fn eq(&self, other: &Self) -> bool {
216        self.get() == other.get()
217    }
218}
219
220impl<T: OptionalCompatible + Eq> Eq for Optional<T> {}
221
222impl<T: OptionalCompatible> From<Option<T>> for Optional<T> {
223    #[inline]
224    fn from(value: Option<T>) -> Self {
225        match value {
226            Some(v) => Self::some(v),
227            None => Self::none(),
228        }
229    }
230}
231
232impl<T: OptionalCompatible> From<Optional<T>> for Option<T> {
233    #[inline]
234    fn from(value: Optional<T>) -> Self {
235        value.into_option()
236    }
237}
238
239impl<T: OptionalCompatible + Debug> Debug for Optional<T> {
240    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241        match self.get() {
242            Some(v) => write!(f, "Optional::Some({v:?})"),
243            None => f.write_str("Optional::None"),
244        }
245    }
246}