From b505a2da0ddb8cb46d904ae63b32bbe3079389c5 Mon Sep 17 00:00:00 2001 From: Joshix Date: Mon, 1 Sep 2025 19:00:00 +0000 Subject: [PATCH 1/5] From for FixedString --- src/inline.rs | 18 +++++++++++++++ src/string.rs | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/src/inline.rs b/src/inline.rs index 5d66fb7..c0d75b3 100644 --- a/src/inline.rs +++ b/src/inline.rs @@ -79,6 +79,24 @@ impl + AsMut<[u8]> + Default + TypeSize> InlineStrin Some(Self { arr }) } + pub fn from_char(val: char) -> Option { + let len = val.len_utf8(); + + let mut arr = StrRepr::default(); + if len > size_of::() { + return None; + } + + val.encode_utf8(arr.as_mut()); + + if len != Self::max_len() { + // 0xFF terminate the string, to gain an extra inline character + arr.as_mut()[len] = Self::TERMINATOR; + } + + Some(Self { arr }) + } + pub fn len(&self) -> u8 { // Copy to a temporary, 16 byte array to allow for SIMD impl. let mut buf = [0_u8; 16]; diff --git a/src/string.rs b/src/string.rs index 93adfd1..e53ee37 100644 --- a/src/string.rs +++ b/src/string.rs @@ -294,6 +294,24 @@ impl TryFrom for FixedString { } } +impl From for FixedString { + fn from(value: char) -> Self { + if let Some(value) = InlineString::from_char(value) { + return Self(FixedStringRepr::Inline(value)); + } + + let mut bytes = vec![0; value.len_utf8()].into_boxed_slice(); + + value.encode_utf8(&mut bytes); + + let bytes = bytes + .try_into() + .expect("len_utf8 is at most 4, so it will fit in u8"); + + Self(FixedStringRepr::Heap(bytes)) + } +} + impl From> for String { fn from(value: FixedString) -> Self { match value.0 { @@ -474,4 +492,50 @@ mod test { assert_eq!(core::mem::size_of::>(), 13); assert_eq!(core::mem::align_of::>(), 1); } + + #[test] + fn from_char_u8() { + let s: FixedString = 'a'.into(); + assert_eq!(s.len(), 1); + + let s: FixedString = '¼'.into(); + assert_eq!(s.len(), 2); + + let s: FixedString = '⚡'.into(); + assert_eq!(s.len(), 3); + + let s: FixedString = '🦀'.into(); + assert_eq!(s.len(), 4); + } + + #[test] + fn from_char_u16() { + let s: FixedString = 'a'.into(); + assert_eq!(s.len(), 1); + + let s: FixedString = '¼'.into(); + assert_eq!(s.len(), 2); + + let s: FixedString = '⚡'.into(); + assert_eq!(s.len(), 3); + + let s: FixedString = '🦀'.into(); + assert_eq!(s.len(), 4); + } + + #[test] + #[cfg(any(target_pointer_width = "64", target_pointer_width = "32"))] + fn from_char_u32() { + let s: FixedString = 'a'.into(); + assert_eq!(s.len(), 1); + + let s: FixedString = '¼'.into(); + assert_eq!(s.len(), 2); + + let s: FixedString = '⚡'.into(); + assert_eq!(s.len(), 3); + + let s: FixedString = '🦀'.into(); + assert_eq!(s.len(), 4); + } } From 931442fe39f575e49ac2ed95c8c27b5d7cd2cb57 Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 2 Sep 2025 14:00:00 +0000 Subject: [PATCH 2/5] deduplicate code --- src/inline.rs | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/src/inline.rs b/src/inline.rs index c0d75b3..30b569b 100644 --- a/src/inline.rs +++ b/src/inline.rs @@ -63,31 +63,14 @@ impl + AsMut<[u8]> + Default + TypeSize> InlineStrin StrRepr::default().as_ref().len() } - pub fn from_str(val: &str) -> Option { - let mut arr = StrRepr::default(); - if val.len() > size_of::() { - return None; - } - - arr.as_mut()[..val.len()].copy_from_slice(val.as_bytes()); - - if val.len() != Self::max_len() { - // 0xFF terminate the string, to gain an extra inline character - arr.as_mut()[val.len()] = Self::TERMINATOR; - } - - Some(Self { arr }) - } - - pub fn from_char(val: char) -> Option { - let len = val.len_utf8(); - + #[inline] + fn from_len_and_write(len: usize, write: impl FnOnce(&mut [u8])) -> Option { let mut arr = StrRepr::default(); if len > size_of::() { return None; } - val.encode_utf8(arr.as_mut()); + write(arr.as_mut()); if len != Self::max_len() { // 0xFF terminate the string, to gain an extra inline character @@ -97,6 +80,18 @@ impl + AsMut<[u8]> + Default + TypeSize> InlineStrin Some(Self { arr }) } + pub fn from_str(val: &str) -> Option { + Self::from_len_and_write(val.len(), |arr| { + arr[..val.len()].copy_from_slice(val.as_bytes()); + }) + } + + pub fn from_char(val: char) -> Option { + Self::from_len_and_write(val.len_utf8(), |arr| { + val.encode_utf8(arr); + }) + } + pub fn len(&self) -> u8 { // Copy to a temporary, 16 byte array to allow for SIMD impl. let mut buf = [0_u8; 16]; From 7a21c43e17bac164e6b0d22839f85c635407e2cd Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 2 Sep 2025 15:00:00 +0000 Subject: [PATCH 3/5] use alloc::vec; --- src/string.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/string.rs b/src/string.rs index e53ee37..ebf64b2 100644 --- a/src/string.rs +++ b/src/string.rs @@ -296,6 +296,8 @@ impl TryFrom for FixedString { impl From for FixedString { fn from(value: char) -> Self { + use alloc::vec; + if let Some(value) = InlineString::from_char(value) { return Self(FixedStringRepr::Inline(value)); } From 7ca78a6f1da99c014dfb524bedbc9bc8f14e61a0 Mon Sep 17 00:00:00 2001 From: Joshix Date: Fri, 5 Sep 2025 17:00:00 +0000 Subject: [PATCH 4/5] test that the strings are inline --- src/string.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/string.rs b/src/string.rs index ebf64b2..718eeca 100644 --- a/src/string.rs +++ b/src/string.rs @@ -499,30 +499,38 @@ mod test { fn from_char_u8() { let s: FixedString = 'a'.into(); assert_eq!(s.len(), 1); + assert!(s.is_inline()); let s: FixedString = '¼'.into(); assert_eq!(s.len(), 2); + assert!(s.is_inline()); let s: FixedString = '⚡'.into(); assert_eq!(s.len(), 3); + assert!(s.is_inline()); let s: FixedString = '🦀'.into(); assert_eq!(s.len(), 4); + assert!(s.is_inline()); } #[test] fn from_char_u16() { let s: FixedString = 'a'.into(); assert_eq!(s.len(), 1); + assert!(s.is_inline()); let s: FixedString = '¼'.into(); assert_eq!(s.len(), 2); + assert!(s.is_inline()); let s: FixedString = '⚡'.into(); assert_eq!(s.len(), 3); + assert!(s.is_inline()); let s: FixedString = '🦀'.into(); assert_eq!(s.len(), 4); + assert!(s.is_inline()); } #[test] @@ -530,14 +538,18 @@ mod test { fn from_char_u32() { let s: FixedString = 'a'.into(); assert_eq!(s.len(), 1); + assert!(s.is_inline()); let s: FixedString = '¼'.into(); assert_eq!(s.len(), 2); + assert!(s.is_inline()); let s: FixedString = '⚡'.into(); assert_eq!(s.len(), 3); + assert!(s.is_inline()); let s: FixedString = '🦀'.into(); assert_eq!(s.len(), 4); + assert!(s.is_inline()); } } From 4cd5632d27f33242028fcad74516c343b0391411 Mon Sep 17 00:00:00 2001 From: Joshix Date: Sun, 7 Sep 2025 12:00:00 +0000 Subject: [PATCH 5/5] 4 byte char doesn't fit on 16bit --- src/string.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/string.rs b/src/string.rs index 718eeca..907dee1 100644 --- a/src/string.rs +++ b/src/string.rs @@ -511,6 +511,7 @@ mod test { let s: FixedString = '🦀'.into(); assert_eq!(s.len(), 4); + #[cfg(any(target_pointer_width = "64", target_pointer_width = "32"))] assert!(s.is_inline()); }