pub const DUCK_STRING_SIZE: usize = 16;
pub const DUCK_STRING_INLINE_MAX_LEN: usize = 12;
#[derive(Debug, Clone, Copy)]
pub struct DuckStringView<'a> {
bytes: &'a [u8],
length: usize,
}
impl<'a> DuckStringView<'a> {
#[must_use]
pub const fn from_bytes(raw: &'a [u8; DUCK_STRING_SIZE]) -> Self {
let length = u32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]) as usize;
Self { bytes: raw, length }
}
#[must_use]
#[inline]
pub const fn len(&self) -> usize {
self.length
}
#[must_use]
#[inline]
pub const fn is_empty(&self) -> bool {
self.length == 0
}
#[must_use]
pub fn as_str(&self) -> Option<&'a str> {
let slice = self.as_bytes_unsafe()?;
std::str::from_utf8(slice).ok()
}
fn as_bytes_unsafe(&self) -> Option<&'a [u8]> {
if self.length <= DUCK_STRING_INLINE_MAX_LEN {
Some(&self.bytes[4..4 + self.length])
} else {
let ptr_bytes: [u8; 8] = self.bytes[8..16].try_into().ok()?;
let ptr_val = usize::from_le_bytes(ptr_bytes) as *const u8;
if ptr_val.is_null() {
return None;
}
Some(unsafe { std::slice::from_raw_parts(ptr_val, self.length) })
}
}
}
pub unsafe fn read_duck_string<'a>(data: *const u8, idx: usize) -> &'a str {
let str_ptr = unsafe { data.add(idx * DUCK_STRING_SIZE) };
let raw_bytes: &'a [u8; DUCK_STRING_SIZE] =
unsafe { &*str_ptr.cast::<[u8; DUCK_STRING_SIZE]>() };
DuckStringView::from_bytes(raw_bytes).as_str().unwrap_or("")
}
#[cfg(test)]
mod tests {
use super::*;
fn make_inline_bytes(s: &str) -> [u8; 16] {
assert!(
s.len() <= DUCK_STRING_INLINE_MAX_LEN,
"use pointer format for long strings"
);
let mut bytes = [0u8; 16];
let len = u32::try_from(s.len()).unwrap_or(u32::MAX);
bytes[..4].copy_from_slice(&len.to_le_bytes());
bytes[4..4 + s.len()].copy_from_slice(s.as_bytes());
bytes
}
#[test]
fn empty_string_inline() {
let bytes = make_inline_bytes("");
let view = DuckStringView::from_bytes(&bytes);
assert_eq!(view.len(), 0);
assert!(view.is_empty());
assert_eq!(view.as_str(), Some(""));
}
#[test]
fn short_string_inline() {
let bytes = make_inline_bytes("hello");
let view = DuckStringView::from_bytes(&bytes);
assert_eq!(view.len(), 5);
assert!(!view.is_empty());
assert_eq!(view.as_str(), Some("hello"));
}
#[test]
fn max_inline_string() {
let s = "abcdefghijkl"; assert_eq!(s.len(), DUCK_STRING_INLINE_MAX_LEN);
let bytes = make_inline_bytes(s);
let view = DuckStringView::from_bytes(&bytes);
assert_eq!(view.len(), 12);
assert_eq!(view.as_str(), Some(s));
}
#[test]
fn pointer_format_string() {
let long_str = "this is a longer string that exceeds 12 bytes";
let len = long_str.len();
let ptr = long_str.as_ptr();
let mut bytes = [0u8; 16];
bytes[..4].copy_from_slice(&u32::try_from(len).unwrap_or(u32::MAX).to_le_bytes());
bytes[4..8].copy_from_slice(&long_str.as_bytes()[..4]);
let ptr_val = ptr as usize;
bytes[8..16].copy_from_slice(&ptr_val.to_le_bytes());
let view = DuckStringView::from_bytes(&bytes);
assert_eq!(view.len(), len);
assert_eq!(view.as_str(), Some(long_str));
}
#[test]
fn pointer_null_returns_none() {
let mut bytes = [0u8; 16];
bytes[..4].copy_from_slice(&13u32.to_le_bytes());
let view = DuckStringView::from_bytes(&bytes);
assert!(view.as_str().is_none());
}
#[test]
fn read_duck_string_inline() {
let bytes = make_inline_bytes("world");
let data = bytes.as_ptr();
let s = unsafe { read_duck_string(data, 0) };
assert_eq!(s, "world");
}
#[test]
fn read_duck_string_pointer_format() {
let long_str = "abcdefghijklmnopqrst"; let len = long_str.len();
let ptr = long_str.as_ptr();
let mut bytes = [0u8; 16];
bytes[..4].copy_from_slice(&u32::try_from(len).unwrap_or(u32::MAX).to_le_bytes());
bytes[4..8].copy_from_slice(&long_str.as_bytes()[..4]);
let ptr_val = ptr as usize;
bytes[8..16].copy_from_slice(&ptr_val.to_le_bytes());
let s = unsafe { read_duck_string(bytes.as_ptr(), 0) };
assert_eq!(s, long_str);
}
#[test]
fn duck_string_size_constant() {
assert_eq!(DUCK_STRING_SIZE, 16);
}
#[test]
fn duck_string_inline_max_len_constant() {
assert_eq!(DUCK_STRING_INLINE_MAX_LEN, 12);
}
}