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        // Get a mutable pointer to the inner value directly.
150        // We can't use get_value() because it creates a shared reference via as_ref(),
151        // which would violate Stacked Borrows when we try to drop through a mutable pointer.
152        // Instead, call the typed drop function which takes a mutable pointer.
153        unsafe { option_drop_inner(ptr, def) };
154    }
155}
156
157/// Helper to drop the inner value of a Some option with proper mutable access
158unsafe fn option_drop_inner(ptr: crate::PtrMut, def: &OptionDef) {
159    // Use the replace_with vtable function to replace Some with None.
160    // This properly handles the drop of the inner value.
161    unsafe { (def.vtable.replace_with)(ptr, None) };
162}
163
164/// Default for `Option<T>` - always None (no `T::Default` requirement)
165unsafe fn option_default<T>(ox: OxPtrMut) {
166    let ptr = ox.ptr();
167    unsafe { ptr.as_uninit().put(Option::<T>::None) };
168}
169
170/// Check if `Option<T>` is Some
171unsafe fn option_is_some<T>(option: PtrConst) -> bool {
172    unsafe { option.get::<Option<T>>().is_some() }
173}
174
175/// Get the value from `Option<T>` if present
176unsafe fn option_get_value<T>(option: PtrConst) -> Option<PtrConst> {
177    unsafe {
178        option
179            .get::<Option<T>>()
180            .as_ref()
181            .map(|t| PtrConst::new(t as *const T))
182    }
183}
184
185/// Initialize `Option<T>` with Some(value)
186unsafe fn option_init_some<T>(option: crate::PtrUninit, value: PtrConst) -> crate::PtrMut {
187    unsafe { option.put(Option::Some(value.read::<T>())) }
188}
189
190/// Initialize `Option<T>` with None
191unsafe fn option_init_none<T>(option: crate::PtrUninit) -> crate::PtrMut {
192    unsafe { option.put(<Option<T>>::None) }
193}
194
195/// Replace `Option<T>` with a new value
196unsafe fn option_replace_with<T>(option: crate::PtrMut, value: Option<PtrConst>) {
197    unsafe {
198        let option = option.as_mut::<Option<T>>();
199        match value {
200            Some(value) => {
201                option.replace(value.read::<T>());
202            }
203            None => {
204                option.take();
205            }
206        };
207    }
208}
209
210unsafe impl<'a, T: Facet<'a>> Facet<'a> for Option<T> {
211    const SHAPE: &'static Shape = &const {
212        const fn build_option_vtable<T>() -> OptionVTable {
213            OptionVTable::builder()
214                .is_some(option_is_some::<T>)
215                .get_value(option_get_value::<T>)
216                .init_some(option_init_some::<T>)
217                .init_none(option_init_none::<T>)
218                .replace_with(option_replace_with::<T>)
219                .build()
220        }
221
222        const fn build_vtable() -> VTableIndirect {
223            VTableIndirect {
224                display: Some(option_display),
225                debug: Some(option_debug),
226                hash: Some(option_hash),
227                invariants: None,
228                parse: None,
229                parse_bytes: None,
230                try_from: None,
231                try_into_inner: None,
232                try_borrow_inner: None,
233                partial_eq: Some(option_partial_eq),
234                partial_cmp: Some(option_partial_cmp),
235                cmp: Some(option_cmp),
236            }
237        }
238
239        const fn build_type_ops<T>() -> TypeOpsIndirect {
240            TypeOpsIndirect {
241                drop_in_place: option_drop,
242                default_in_place: Some(option_default::<T>),
243                clone_into: None,
244                is_truthy: Some(option_is_some::<T>),
245            }
246        }
247
248        ShapeBuilder::for_sized::<Option<T>>("Option")
249            .ty(Type::User(
250                // Null-Pointer-Optimization check
251                if core::mem::size_of::<T>() == core::mem::size_of::<Option<T>>()
252                    && core::mem::size_of::<T>() <= core::mem::size_of::<usize>()
253                {
254                    UserType::Enum(EnumType {
255                        repr: Repr::default(),
256                        enum_repr: EnumRepr::RustNPO,
257                        variants: &const {
258                            [
259                                VariantBuilder::unit("None").discriminant(0).build(),
260                                VariantBuilder::tuple(
261                                    "Some",
262                                    &const { [FieldBuilder::new("0", crate::shape_of::<T>, 0).build()] },
263                                )
264                                .discriminant(0)
265                                .build(),
266                            ]
267                        },
268                    })
269                } else {
270                    UserType::Opaque
271                },
272            ))
273            .def(Def::Option(OptionDef::new(
274                &const { build_option_vtable::<T>() },
275                T::SHAPE,
276            )))
277            .type_params(&[TypeParam {
278                name: "T",
279                shape: T::SHAPE,
280            }])
281            .inner(T::SHAPE)
282            .vtable_indirect(&const { build_vtable() })
283            .type_ops_indirect(&const { build_type_ops::<T>() })
284            // Option<T> propagates T's variance
285            .variance(Shape::computed_variance)
286            .build()
287    };
288}