facet_core/impls/core/
option.rs

1//! Facet implementation for `Option<T>`
2
3use core::cmp::Ordering;
4
5use crate::{
6    Def, EnumRepr, EnumType, Facet, FieldBuilder, HashProxy, OptionDef, OptionVTable, OxPtrConst,
7    OxPtrMut, OxRef, PtrConst, Repr, Shape, ShapeBuilder, Type, TypeOpsIndirect, TypeParam,
8    UserType, VTableIndirect, VariantBuilder,
9};
10
11/// Extract the OptionDef from a shape, returns None if not an Option
12#[inline]
13fn get_option_def(shape: &'static Shape) -> Option<&'static OptionDef> {
14    match shape.def {
15        Def::Option(ref def) => Some(def),
16        _ => None,
17    }
18}
19
20/// Display for `Option<T>` - delegates to inner T's display if available
21unsafe fn option_display(
22    ox: OxPtrConst,
23    f: &mut core::fmt::Formatter<'_>,
24) -> Option<core::fmt::Result> {
25    let shape = ox.shape();
26    let def = get_option_def(shape)?;
27    let ptr = ox.ptr();
28
29    if unsafe { (def.vtable.is_some)(ptr) } {
30        // Get the inner value using the vtable
31        let inner_ptr = unsafe { (def.vtable.get_value)(ptr)? };
32        // Delegate to inner type's display
33        unsafe { def.t.call_display(inner_ptr, f) }
34    } else {
35        Some(f.write_str("None"))
36    }
37}
38
39/// Debug for `Option<T>` - delegates to inner T's debug if available
40unsafe fn option_debug(
41    ox: OxPtrConst,
42    f: &mut core::fmt::Formatter<'_>,
43) -> Option<core::fmt::Result> {
44    let shape = ox.shape();
45    let def = get_option_def(shape)?;
46    let ptr = ox.ptr();
47
48    if unsafe { (def.vtable.is_some)(ptr) } {
49        // Get the inner value using the vtable
50        let inner_ptr = unsafe { (def.vtable.get_value)(ptr)? };
51        let inner_ox = OxRef::new(inner_ptr, def.t);
52        Some(f.debug_tuple("Some").field(&inner_ox).finish())
53    } else {
54        Some(f.write_str("None"))
55    }
56}
57
58/// Hash for `Option<T>` - delegates to inner T's hash if available
59unsafe fn option_hash(ox: OxPtrConst, hasher: &mut HashProxy<'_>) -> Option<()> {
60    let shape = ox.shape();
61    let def = get_option_def(shape)?;
62    let ptr = ox.ptr();
63
64    use core::hash::Hash;
65    if unsafe { (def.vtable.is_some)(ptr) } {
66        1u8.hash(hasher);
67        let inner_ptr = unsafe { (def.vtable.get_value)(ptr)? };
68        unsafe { def.t.call_hash(inner_ptr, hasher)? };
69    } else {
70        0u8.hash(hasher);
71    }
72    Some(())
73}
74
75/// PartialEq for `Option<T>`
76unsafe fn option_partial_eq(a: OxPtrConst, b: OxPtrConst) -> Option<bool> {
77    let shape = a.shape();
78    let def = get_option_def(shape)?;
79
80    let a_ptr = a.ptr();
81    let b_ptr = b.ptr();
82    let a_is_some = unsafe { (def.vtable.is_some)(a_ptr) };
83    let b_is_some = unsafe { (def.vtable.is_some)(b_ptr) };
84
85    Some(match (a_is_some, b_is_some) {
86        (false, false) => true,
87        (true, true) => {
88            let a_inner = unsafe { (def.vtable.get_value)(a_ptr)? };
89            let b_inner = unsafe { (def.vtable.get_value)(b_ptr)? };
90            unsafe { def.t.call_partial_eq(a_inner, b_inner)? }
91        }
92        _ => false,
93    })
94}
95
96/// PartialOrd for `Option<T>`
97unsafe fn option_partial_cmp(a: OxPtrConst, b: OxPtrConst) -> Option<Option<Ordering>> {
98    let shape = a.shape();
99    let def = get_option_def(shape)?;
100
101    let a_ptr = a.ptr();
102    let b_ptr = b.ptr();
103    let a_is_some = unsafe { (def.vtable.is_some)(a_ptr) };
104    let b_is_some = unsafe { (def.vtable.is_some)(b_ptr) };
105
106    Some(match (a_is_some, b_is_some) {
107        (false, false) => Some(Ordering::Equal),
108        (false, true) => Some(Ordering::Less),
109        (true, false) => Some(Ordering::Greater),
110        (true, true) => {
111            let a_inner = unsafe { (def.vtable.get_value)(a_ptr)? };
112            let b_inner = unsafe { (def.vtable.get_value)(b_ptr)? };
113            unsafe { def.t.call_partial_cmp(a_inner, b_inner)? }
114        }
115    })
116}
117
118/// Ord for `Option<T>`
119unsafe fn option_cmp(a: OxPtrConst, b: OxPtrConst) -> Option<Ordering> {
120    let shape = a.shape();
121    let def = get_option_def(shape)?;
122
123    let a_ptr = a.ptr();
124    let b_ptr = b.ptr();
125    let a_is_some = unsafe { (def.vtable.is_some)(a_ptr) };
126    let b_is_some = unsafe { (def.vtable.is_some)(b_ptr) };
127
128    Some(match (a_is_some, b_is_some) {
129        (false, false) => Ordering::Equal,
130        (false, true) => Ordering::Less,
131        (true, false) => Ordering::Greater,
132        (true, true) => {
133            let a_inner = unsafe { (def.vtable.get_value)(a_ptr)? };
134            let b_inner = unsafe { (def.vtable.get_value)(b_ptr)? };
135            unsafe { def.t.call_cmp(a_inner, b_inner)? }
136        }
137    })
138}
139
140/// Drop for `Option<T>`
141unsafe fn option_drop(ox: OxPtrMut) {
142    let shape = ox.shape();
143    let Some(def) = get_option_def(shape) else {
144        return;
145    };
146    let ptr = ox.ptr();
147
148    if unsafe { (def.vtable.is_some)(ptr.as_const()) } {
149        let Some(inner_ptr) = (unsafe { (def.vtable.get_value)(ptr.as_const()) }) else {
150            return;
151        };
152        // We need a mutable pointer to the inner value for drop
153        // The inner value is at the same location as the option for NPO types
154        let inner_ptr_mut = crate::PtrMut::new(inner_ptr.as_byte_ptr() as *mut u8);
155        unsafe { def.t.call_drop_in_place(inner_ptr_mut) };
156    }
157}
158
159/// Default for `Option<T>` - always None (no `T::Default` requirement)
160unsafe fn option_default<T>(ox: OxPtrMut) {
161    let ptr = ox.ptr();
162    unsafe { ptr.as_uninit().put(Option::<T>::None) };
163}
164
165/// Check if `Option<T>` is Some
166unsafe fn option_is_some<T>(option: PtrConst) -> bool {
167    unsafe { option.get::<Option<T>>().is_some() }
168}
169
170/// Get the value from `Option<T>` if present
171unsafe fn option_get_value<T>(option: PtrConst) -> Option<PtrConst> {
172    unsafe {
173        option
174            .get::<Option<T>>()
175            .as_ref()
176            .map(|t| PtrConst::new(t as *const T))
177    }
178}
179
180/// Initialize `Option<T>` with Some(value)
181unsafe fn option_init_some<T>(option: crate::PtrUninit, value: PtrConst) -> crate::PtrMut {
182    unsafe { option.put(Option::Some(value.read::<T>())) }
183}
184
185/// Initialize `Option<T>` with None
186unsafe fn option_init_none<T>(option: crate::PtrUninit) -> crate::PtrMut {
187    unsafe { option.put(<Option<T>>::None) }
188}
189
190/// Replace `Option<T>` with a new value
191unsafe fn option_replace_with<T>(option: crate::PtrMut, value: Option<PtrConst>) {
192    unsafe {
193        let option = option.as_mut::<Option<T>>();
194        match value {
195            Some(value) => {
196                option.replace(value.read::<T>());
197            }
198            None => {
199                option.take();
200            }
201        };
202    }
203}
204
205unsafe impl<'a, T: Facet<'a>> Facet<'a> for Option<T> {
206    const SHAPE: &'static Shape = &const {
207        const fn build_option_vtable<T>() -> OptionVTable {
208            OptionVTable::builder()
209                .is_some(option_is_some::<T>)
210                .get_value(option_get_value::<T>)
211                .init_some(option_init_some::<T>)
212                .init_none(option_init_none::<T>)
213                .replace_with(option_replace_with::<T>)
214                .build()
215        }
216
217        const fn build_vtable() -> VTableIndirect {
218            VTableIndirect {
219                display: Some(option_display),
220                debug: Some(option_debug),
221                hash: Some(option_hash),
222                invariants: None,
223                parse: None,
224                try_from: None,
225                try_into_inner: None,
226                try_borrow_inner: None,
227                partial_eq: Some(option_partial_eq),
228                partial_cmp: Some(option_partial_cmp),
229                cmp: Some(option_cmp),
230            }
231        }
232
233        const fn build_type_ops<T>() -> TypeOpsIndirect {
234            TypeOpsIndirect {
235                drop_in_place: option_drop,
236                default_in_place: Some(option_default::<T>),
237                clone_into: None,
238                is_truthy: Some(option_is_some::<T>),
239            }
240        }
241
242        ShapeBuilder::for_sized::<Option<T>>("Option")
243            .ty(Type::User(
244                // Null-Pointer-Optimization check
245                if core::mem::size_of::<T>() == core::mem::size_of::<Option<T>>()
246                    && core::mem::size_of::<T>() <= core::mem::size_of::<usize>()
247                {
248                    UserType::Enum(EnumType {
249                        repr: Repr::default(),
250                        enum_repr: EnumRepr::RustNPO,
251                        variants: &const {
252                            [
253                                VariantBuilder::unit("None").discriminant(0).build(),
254                                VariantBuilder::tuple(
255                                    "Some",
256                                    &const { [FieldBuilder::new("0", crate::shape_of::<T>, 0).build()] },
257                                )
258                                .discriminant(0)
259                                .build(),
260                            ]
261                        },
262                    })
263                } else {
264                    UserType::Opaque
265                },
266            ))
267            .def(Def::Option(OptionDef::new(
268                &const { build_option_vtable::<T>() },
269                T::SHAPE,
270            )))
271            .type_params(&[TypeParam {
272                name: "T",
273                shape: T::SHAPE,
274            }])
275            .inner(T::SHAPE)
276            .vtable_indirect(&const { build_vtable() })
277            .type_ops_indirect(&const { build_type_ops::<T>() })
278            // Option<T> propagates T's variance
279            .variance(Shape::computed_variance)
280            .build()
281    };
282}