Skip to main content

gc_alloc/
string.rs

1use std::{fmt::Write, ptr::NonNull};
2
3use crate::{GcToken, gc};
4
5pub fn from_str(_token: &impl GcToken, s: &str) -> GcString {
6    let ptr = alloc(s.len());
7    unsafe { std::ptr::copy_nonoverlapping(s.as_ptr(), ptr.as_ptr(), s.len()) };
8    GcString(unsafe {
9        std::str::from_utf8_unchecked_mut(std::slice::from_raw_parts_mut(ptr.as_ptr(), s.len()))
10            .into()
11    })
12}
13
14pub fn from_iter<I: IntoIterator<Item = char>>(token: &impl GcToken, iter: I) -> GcString {
15    let iter = iter.into_iter();
16    let (lower, _) = iter.size_hint();
17
18    let mut formatter = Formatter::with_capacity(token, lower);
19    for c in iter {
20        let _ = formatter.write_char(c);
21    }
22    formatter.finish()
23}
24
25pub struct Formatter {
26    buf: NonNull<u8>,
27    len: usize,
28    cap: usize,
29}
30
31impl Formatter {
32    pub fn new(_token: &impl GcToken) -> Self {
33        Self {
34            buf: NonNull::dangling(),
35            len: 0,
36            cap: 0,
37        }
38    }
39
40    pub fn with_capacity(_token: &impl GcToken, cap: usize) -> Self {
41        let buf = if cap == 0 {
42            NonNull::dangling()
43        } else {
44            alloc(cap)
45        };
46        Self { buf, len: 0, cap }
47    }
48}
49
50impl Write for Formatter {
51    #[inline]
52    fn write_str(&mut self, s: &str) -> std::fmt::Result {
53        let mut cap = self.cap;
54        while self.len + s.len() > cap {
55            cap = cap.max(1).checked_mul(2).expect("Capacity overflow");
56        }
57        if cap != self.cap {
58            let new_buf = alloc(cap);
59            unsafe { std::ptr::copy_nonoverlapping(self.buf.as_ptr(), new_buf.as_ptr(), self.len) };
60            self.buf = new_buf;
61            self.cap = cap;
62        }
63        unsafe {
64            std::ptr::copy_nonoverlapping(s.as_ptr(), self.buf.add(self.len).as_ptr(), s.len())
65        };
66        self.len += s.len();
67        Ok(())
68    }
69}
70
71impl Formatter {
72    pub fn finish(self) -> GcString {
73        if self.len == 0 {
74            return GcString("".into());
75        }
76        GcString(unsafe {
77            std::str::from_utf8_unchecked(std::slice::from_raw_parts(self.buf.as_ptr(), self.len))
78                .into()
79        })
80    }
81}
82
83#[macro_export]
84#[doc(hidden)]
85macro_rules! format {
86    ($token:expr, $($arg:tt)*) => {{
87        let mut formatter = $crate::string::Formatter::new($token);
88        let _ = std::fmt::write(&mut formatter, format_args!($($arg)*));
89        formatter.finish()
90    }};
91}
92
93pub use crate::format;
94
95pub struct GcString(NonNull<str>);
96
97impl GcString {
98    pub fn as_ptr(&self) -> *mut str {
99        self.0.as_ptr()
100    }
101
102    pub fn as_ref<'gc>(&self, _token: &'gc impl GcToken) -> &'gc str {
103        unsafe { &*self.as_ptr() }
104    }
105
106    #[allow(clippy::mut_from_ref)]
107    pub fn as_mut<'gc>(&mut self, _token: &'gc impl GcToken) -> &'gc mut str {
108        unsafe { &mut *self.as_ptr() }
109    }
110
111    /// # Safety
112    /// The returned reference cannot be used in a thread that is not registered with the GC.
113    pub unsafe fn as_ref_unconstrained(&self) -> &'static mut str {
114        unsafe { &mut *self.as_ptr() }
115    }
116}
117
118fn alloc(cap: usize) -> NonNull<u8> {
119    let ptr = unsafe { gc::GC_malloc_atomic(cap) as *mut u8 };
120    std::ptr::NonNull::new(ptr).expect("GC_malloc_atomic failed")
121}