• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

facet-rs / facet / 19770016110

28 Nov 2025 05:06PM UTC coverage: 60.673% (+0.3%) from 60.41%
19770016110

push

github

fasterthanlime
Add facet-value crate for memory-efficient dynamic values

A new crate inspired by ijson that provides a pointer-sized Value type
using tagged pointers. Key features:

- Seven value types: Null, Bool, Number, String, Bytes, Array, Object
- Inline values for null/true/false (no heap allocation)
- VBytes type for binary format support (MessagePack, CBOR, etc.)
- no_std compatible with alloc feature
- Struct-based API with destructure() methods (not exposed enum)

995 of 1530 new or added lines in 6 files covered. (65.03%)

16306 of 26875 relevant lines covered (60.67%)

152.08 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

66.11
/facet-value/src/string.rs
1
//! String value type.
2

3
#[cfg(feature = "alloc")]
4
use alloc::alloc::{Layout, alloc, dealloc};
5
#[cfg(feature = "alloc")]
6
use alloc::string::String;
7
use core::borrow::Borrow;
8
use core::cmp::Ordering;
9
use core::fmt::{self, Debug, Formatter};
10
use core::hash::{Hash, Hasher};
11
use core::ops::Deref;
12
use core::ptr;
13

14
use crate::value::{TypeTag, Value};
15

16
/// Header for heap-allocated strings.
17
#[repr(C, align(8))]
18
struct StringHeader {
19
    /// Length of the string in bytes
20
    len: usize,
21
    // String data follows immediately after
22
}
23

24
/// A string value.
25
///
26
/// `VString` stores UTF-8 string data. Unlike some implementations, strings are
27
/// not interned - each `VString` owns its own copy of the data.
28
#[repr(transparent)]
29
#[derive(Clone)]
30
pub struct VString(pub(crate) Value);
31

32
impl VString {
33
    fn layout(len: usize) -> Layout {
60✔
34
        Layout::new::<StringHeader>()
60✔
35
            .extend(Layout::array::<u8>(len).unwrap())
60✔
36
            .unwrap()
60✔
37
            .0
60✔
38
            .pad_to_align()
60✔
39
    }
60✔
40

41
    #[cfg(feature = "alloc")]
42
    fn alloc(s: &str) -> *mut StringHeader {
29✔
43
        unsafe {
44
            let layout = Self::layout(s.len());
29✔
45
            let ptr = alloc(layout).cast::<StringHeader>();
29✔
46
            (*ptr).len = s.len();
29✔
47

48
            // Copy string data
49
            let data_ptr = ptr.add(1).cast::<u8>();
29✔
50
            ptr::copy_nonoverlapping(s.as_ptr(), data_ptr, s.len());
29✔
51

52
            ptr
29✔
53
        }
54
    }
29✔
55

56
    #[cfg(feature = "alloc")]
57
    fn dealloc_ptr(ptr: *mut StringHeader) {
30✔
58
        unsafe {
30✔
59
            let len = (*ptr).len;
30✔
60
            let layout = Self::layout(len);
30✔
61
            dealloc(ptr.cast::<u8>(), layout);
30✔
62
        }
30✔
63
    }
30✔
64

65
    fn header(&self) -> &StringHeader {
130✔
66
        unsafe { &*(self.0.heap_ptr() as *const StringHeader) }
130✔
67
    }
130✔
68

69
    fn data_ptr(&self) -> *const u8 {
63✔
70
        unsafe { (self.header() as *const StringHeader).add(1).cast() }
63✔
71
    }
63✔
72

73
    /// Creates a new string from a `&str`.
74
    #[cfg(feature = "alloc")]
75
    #[must_use]
76
    pub fn new(s: &str) -> Self {
29✔
77
        if s.is_empty() {
29✔
NEW
78
            return Self::empty();
×
79
        }
29✔
80
        unsafe {
81
            let ptr = Self::alloc(s);
29✔
82
            VString(Value::new_ptr(ptr.cast(), TypeTag::StringOrNull))
29✔
83
        }
84
    }
29✔
85

86
    /// Creates an empty string.
87
    #[cfg(feature = "alloc")]
88
    #[must_use]
89
    pub fn empty() -> Self {
1✔
90
        // For empty strings, we still allocate a header with len=0
91
        // This keeps the code simpler
92
        unsafe {
93
            let layout = Self::layout(0);
1✔
94
            let ptr = alloc(layout).cast::<StringHeader>();
1✔
95
            (*ptr).len = 0;
1✔
96
            VString(Value::new_ptr(ptr.cast(), TypeTag::StringOrNull))
1✔
97
        }
98
    }
1✔
99

100
    /// Returns the length of the string in bytes.
101
    #[must_use]
102
    pub fn len(&self) -> usize {
67✔
103
        self.header().len
67✔
104
    }
67✔
105

106
    /// Returns `true` if the string is empty.
107
    #[must_use]
108
    pub fn is_empty(&self) -> bool {
2✔
109
        self.len() == 0
2✔
110
    }
2✔
111

112
    /// Returns the string as a `&str`.
113
    #[must_use]
114
    pub fn as_str(&self) -> &str {
63✔
115
        unsafe {
116
            let bytes = core::slice::from_raw_parts(self.data_ptr(), self.len());
63✔
117
            core::str::from_utf8_unchecked(bytes)
63✔
118
        }
119
    }
63✔
120

121
    /// Returns the string as a byte slice.
122
    #[must_use]
NEW
123
    pub fn as_bytes(&self) -> &[u8] {
×
NEW
124
        unsafe { core::slice::from_raw_parts(self.data_ptr(), self.len()) }
×
NEW
125
    }
×
126

127
    pub(crate) fn clone_impl(&self) -> Value {
4✔
128
        VString::new(self.as_str()).0
4✔
129
    }
4✔
130

131
    pub(crate) fn drop_impl(&mut self) {
30✔
132
        unsafe {
30✔
133
            Self::dealloc_ptr(self.0.heap_ptr().cast());
30✔
134
        }
30✔
135
    }
30✔
136
}
137

138
impl Deref for VString {
139
    type Target = str;
140

141
    fn deref(&self) -> &str {
2✔
142
        self.as_str()
2✔
143
    }
2✔
144
}
145

146
impl Borrow<str> for VString {
NEW
147
    fn borrow(&self) -> &str {
×
NEW
148
        self.as_str()
×
NEW
149
    }
×
150
}
151

152
impl AsRef<str> for VString {
NEW
153
    fn as_ref(&self) -> &str {
×
NEW
154
        self.as_str()
×
NEW
155
    }
×
156
}
157

158
impl AsRef<[u8]> for VString {
NEW
159
    fn as_ref(&self) -> &[u8] {
×
NEW
160
        self.as_bytes()
×
NEW
161
    }
×
162
}
163

164
impl PartialEq for VString {
165
    fn eq(&self, other: &Self) -> bool {
5✔
166
        self.as_str() == other.as_str()
5✔
167
    }
5✔
168
}
169

170
impl Eq for VString {}
171

172
impl PartialOrd for VString {
173
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1✔
174
        Some(self.cmp(other))
1✔
175
    }
1✔
176
}
177

178
impl Ord for VString {
179
    fn cmp(&self, other: &Self) -> Ordering {
1✔
180
        self.as_str().cmp(other.as_str())
1✔
181
    }
1✔
182
}
183

184
impl Hash for VString {
NEW
185
    fn hash<H: Hasher>(&self, state: &mut H) {
×
NEW
186
        self.as_str().hash(state);
×
NEW
187
    }
×
188
}
189

190
impl Debug for VString {
NEW
191
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
×
NEW
192
        Debug::fmt(self.as_str(), f)
×
NEW
193
    }
×
194
}
195

196
impl fmt::Display for VString {
NEW
197
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
×
NEW
198
        fmt::Display::fmt(self.as_str(), f)
×
NEW
199
    }
×
200
}
201

202
impl Default for VString {
NEW
203
    fn default() -> Self {
×
NEW
204
        Self::empty()
×
NEW
205
    }
×
206
}
207

208
// === PartialEq with str ===
209

210
impl PartialEq<str> for VString {
NEW
211
    fn eq(&self, other: &str) -> bool {
×
NEW
212
        self.as_str() == other
×
NEW
213
    }
×
214
}
215

216
impl PartialEq<VString> for str {
NEW
217
    fn eq(&self, other: &VString) -> bool {
×
NEW
218
        self == other.as_str()
×
NEW
219
    }
×
220
}
221

222
impl PartialEq<&str> for VString {
223
    fn eq(&self, other: &&str) -> bool {
1✔
224
        self.as_str() == *other
1✔
225
    }
1✔
226
}
227

228
#[cfg(feature = "alloc")]
229
impl PartialEq<String> for VString {
NEW
230
    fn eq(&self, other: &String) -> bool {
×
NEW
231
        self.as_str() == other.as_str()
×
NEW
232
    }
×
233
}
234

235
#[cfg(feature = "alloc")]
236
impl PartialEq<VString> for String {
NEW
237
    fn eq(&self, other: &VString) -> bool {
×
NEW
238
        self.as_str() == other.as_str()
×
NEW
239
    }
×
240
}
241

242
// === From implementations ===
243

244
#[cfg(feature = "alloc")]
245
impl From<&str> for VString {
246
    fn from(s: &str) -> Self {
13✔
247
        Self::new(s)
13✔
248
    }
13✔
249
}
250

251
#[cfg(feature = "alloc")]
252
impl From<String> for VString {
NEW
253
    fn from(s: String) -> Self {
×
NEW
254
        Self::new(&s)
×
NEW
255
    }
×
256
}
257

258
#[cfg(feature = "alloc")]
259
impl From<&String> for VString {
NEW
260
    fn from(s: &String) -> Self {
×
NEW
261
        Self::new(s)
×
NEW
262
    }
×
263
}
264

265
#[cfg(feature = "alloc")]
266
impl From<VString> for String {
NEW
267
    fn from(s: VString) -> Self {
×
NEW
268
        s.as_str().into()
×
NEW
269
    }
×
270
}
271

272
// === Value conversions ===
273

274
impl AsRef<Value> for VString {
NEW
275
    fn as_ref(&self) -> &Value {
×
NEW
276
        &self.0
×
NEW
277
    }
×
278
}
279

280
impl AsMut<Value> for VString {
NEW
281
    fn as_mut(&mut self) -> &mut Value {
×
NEW
282
        &mut self.0
×
NEW
283
    }
×
284
}
285

286
impl From<VString> for Value {
NEW
287
    fn from(s: VString) -> Self {
×
NEW
288
        s.0
×
NEW
289
    }
×
290
}
291

292
#[cfg(feature = "alloc")]
293
impl From<&str> for Value {
294
    fn from(s: &str) -> Self {
3✔
295
        VString::new(s).0
3✔
296
    }
3✔
297
}
298

299
#[cfg(feature = "alloc")]
300
impl From<String> for Value {
NEW
301
    fn from(s: String) -> Self {
×
NEW
302
        VString::new(&s).0
×
NEW
303
    }
×
304
}
305

306
#[cfg(feature = "alloc")]
307
impl From<&String> for Value {
NEW
308
    fn from(s: &String) -> Self {
×
NEW
309
        VString::new(s).0
×
NEW
310
    }
×
311
}
312

313
#[cfg(test)]
314
mod tests {
315
    use super::*;
316

317
    #[test]
318
    fn test_new() {
1✔
319
        let s = VString::new("hello");
1✔
320
        assert_eq!(s.as_str(), "hello");
1✔
321
        assert_eq!(s.len(), 5);
1✔
322
        assert!(!s.is_empty());
1✔
323
    }
1✔
324

325
    #[test]
326
    fn test_empty() {
1✔
327
        let s = VString::empty();
1✔
328
        assert_eq!(s.as_str(), "");
1✔
329
        assert_eq!(s.len(), 0);
1✔
330
        assert!(s.is_empty());
1✔
331
    }
1✔
332

333
    #[test]
334
    fn test_equality() {
1✔
335
        let a = VString::new("hello");
1✔
336
        let b = VString::new("hello");
1✔
337
        let c = VString::new("world");
1✔
338

339
        assert_eq!(a, b);
1✔
340
        assert_ne!(a, c);
1✔
341
        assert_eq!(a, "hello");
1✔
342
        assert_eq!(a.as_str(), "hello");
1✔
343
    }
1✔
344

345
    #[test]
346
    fn test_clone() {
1✔
347
        let a = VString::new("test");
1✔
348
        let b = a.clone();
1✔
349
        assert_eq!(a, b);
1✔
350
    }
1✔
351

352
    #[test]
353
    fn test_unicode() {
1✔
354
        let s = VString::new("hello δΈ–η•Œ 🌍");
1✔
355
        assert_eq!(s.as_str(), "hello δΈ–η•Œ 🌍");
1✔
356
    }
1✔
357

358
    #[test]
359
    fn test_deref() {
1✔
360
        let s = VString::new("hello");
1✔
361
        assert!(s.starts_with("hel"));
1✔
362
        assert!(s.ends_with("llo"));
1✔
363
    }
1✔
364

365
    #[test]
366
    fn test_ordering() {
1✔
367
        let a = VString::new("apple");
1✔
368
        let b = VString::new("banana");
1✔
369
        assert!(a < b);
1✔
370
    }
1✔
371
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc