Skip to main content

facet_core/impls/alloc/
string.rs

1use crate::{
2    Def, Facet, PtrConst, Shape, ShapeBuilder, TryFromOutcome, Type, TypeOpsDirect, UserType,
3    VTableDirect, type_ops_direct, vtable_direct,
4};
5
6#[inline(always)]
7unsafe fn string_truthy(value: PtrConst) -> bool {
8    !unsafe { value.get::<alloc::string::String>() }.is_empty()
9}
10
11// TypeOps lifted out - shared static
12static STRING_TYPE_OPS: TypeOpsDirect = TypeOpsDirect {
13    is_truthy: Some(string_truthy),
14    ..type_ops_direct!(alloc::string::String => Default, Clone)
15};
16
17/// Try to convert from &str or String to String
18///
19/// # Safety
20/// `dst` must be valid for writes, `src` must point to valid data of type described by `src_shape`
21unsafe fn string_try_from(
22    dst: *mut alloc::string::String,
23    src_shape: &'static Shape,
24    src: PtrConst,
25) -> TryFromOutcome {
26    // Check if source is &str (Copy type, use get)
27    if src_shape.id == <&str as crate::Facet>::SHAPE.id {
28        let str_ref: &str = unsafe { src.get::<&str>() };
29        unsafe { dst.write(alloc::string::String::from(str_ref)) };
30        return TryFromOutcome::Converted;
31    }
32
33    // Check if source is String (borrow and clone - don't consume since caller might need it)
34    if src_shape.id == <alloc::string::String as crate::Facet>::SHAPE.id {
35        let src_string: &alloc::string::String =
36            unsafe { &*(src.as_byte_ptr() as *const alloc::string::String) };
37        unsafe { dst.write(src_string.clone()) };
38        return TryFromOutcome::Converted;
39    }
40
41    TryFromOutcome::Unsupported
42}
43
44unsafe impl Facet<'_> for alloc::string::String {
45    // String implements: Display, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, FromStr
46    const SHAPE: &'static Shape = &const {
47        const VTABLE: VTableDirect = vtable_direct!(alloc::string::String =>
48            FromStr,
49            Display,
50            Debug,
51            Hash,
52            PartialEq,
53            PartialOrd,
54            Ord,
55            [try_from = string_try_from],
56        );
57
58        ShapeBuilder::for_sized::<alloc::string::String>("String")
59            .module_path("alloc::string")
60            .ty(Type::User(UserType::Opaque))
61            .def(Def::Scalar)
62            .vtable_direct(&VTABLE)
63            .type_ops_direct(&STRING_TYPE_OPS)
64            .eq()
65            .send()
66            .sync()
67            .build()
68    };
69}
70
71#[cfg(test)]
72mod tests {
73    use core::ptr::NonNull;
74
75    use crate::Facet;
76    use alloc::string::String;
77
78    #[test]
79    fn test_string_has_parse() {
80        // Check that String has a parse function in its vtable
81        let shape = String::SHAPE;
82        assert!(
83            shape.vtable.has_parse(),
84            "String should have parse function"
85        );
86    }
87
88    #[test]
89    fn test_string_parse() {
90        // Test that we can parse a string into a String
91        let shape = String::SHAPE;
92
93        // Allocate memory for the String
94        let layout = shape.layout.sized_layout().unwrap();
95        let ptr = unsafe { alloc::alloc::alloc(layout) };
96        let Some(ptr) = NonNull::new(ptr) else {
97            alloc::alloc::handle_alloc_error(layout)
98        };
99        let ptr_uninit = crate::PtrUninit::new(ptr.as_ptr());
100
101        // Parse the string using the new API
102        let result = unsafe { shape.call_parse("hello world", ptr_uninit) };
103        assert!(result.is_some(), "String should have parse function");
104        assert!(result.unwrap().is_ok());
105
106        // Get the parsed value
107        let ptr_mut = unsafe { ptr_uninit.assume_init() };
108        let parsed = unsafe { ptr_mut.get::<String>() };
109        assert_eq!(parsed, &String::from("hello world"));
110
111        // Clean up
112        unsafe {
113            shape.call_drop_in_place(ptr_mut).unwrap();
114            alloc::alloc::dealloc(ptr.as_ptr(), layout);
115        }
116    }
117}