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}