Skip to main content

uqa_core/memory/
string.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! UTF-8 buffers reserve old and replacement allocations before growing.
8
9use super::{reconcile_buffer_capacity, replacement, MemoryBudget, MemoryError, MemoryReservation};
10
11#[derive(Debug)]
12pub struct BudgetedString {
13    value: String,
14    memory: MemoryReservation,
15}
16
17impl BudgetedString {
18    pub fn new(budget: &MemoryBudget) -> Self {
19        Self {
20            value: String::new(),
21            memory: budget.empty_reservation(),
22        }
23    }
24
25    /// Transfer a completed string and its existing allocation lease without copying its buffer.
26    pub fn from_budgeted(value: super::Budgeted<String>) -> Self {
27        let (value, memory) = value.into_parts();
28        Self { value, memory }
29    }
30
31    pub fn capacity(&self) -> usize {
32        self.value.capacity()
33    }
34
35    pub fn reserve(&mut self, additional: usize) -> Result<(), MemoryError> {
36        let required = self
37            .value
38            .len()
39            .checked_add(additional)
40            .ok_or(MemoryError::SizeOverflow)?;
41        if required <= self.capacity() {
42            return Ok(());
43        }
44        let (capacity, mut memory) =
45            replacement::<u8>(self.memory.budget(), self.capacity(), required)?;
46        let mut value = String::new();
47        value.try_reserve_exact(capacity)?;
48        reconcile_buffer_capacity::<u8>(&mut memory, value.capacity())?;
49        value.push_str(&self.value);
50        self.value = value;
51        self.memory = memory;
52        Ok(())
53    }
54
55    pub fn push_str(&mut self, text: &str) -> Result<(), MemoryError> {
56        self.reserve(text.len())?;
57        self.value.push_str(text);
58        Ok(())
59    }
60
61    pub fn push(&mut self, character: char) -> Result<(), MemoryError> {
62        self.reserve(character.len_utf8())?;
63        self.value.push(character);
64        Ok(())
65    }
66
67    pub fn truncate(&mut self, length: usize) {
68        self.value.truncate(length);
69    }
70
71    pub fn into_parts(self) -> (String, MemoryReservation) {
72        (self.value, self.memory)
73    }
74}
75
76impl std::ops::Deref for BudgetedString {
77    type Target = str;
78
79    fn deref(&self) -> &str {
80        &self.value
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn completed_string_transfer_preserves_buffer_and_lease() {
90        let budget = MemoryBudget::new(128);
91        let mut source = BudgetedString::new(&budget);
92        source.push_str("transferred").unwrap();
93        let (value, memory) = source.into_parts();
94        let pointer = value.as_ptr();
95        let capacity = value.capacity();
96        let output = BudgetedString::from_budgeted(super::super::Budgeted::new(value, memory));
97        assert_eq!(output.as_ptr(), pointer);
98        assert_eq!(budget.used(), capacity);
99        drop(output);
100        assert_eq!(budget.used(), 0);
101    }
102}