Skip to content

Commit c93e282

Browse files
jf2048sdroege
authored andcommitted
glib: allow GString to store small inline strings
1 parent dbbd938 commit c93e282

File tree

1 file changed

+85
-53
lines changed

1 file changed

+85
-53
lines changed

glib/src/gstring.rs

Lines changed: 85 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -456,11 +456,16 @@ impl ToOwned for GStr {
456456

457457
#[inline]
458458
fn to_owned(&self) -> Self::Owned {
459-
if self.is_empty() {
460-
return GString::default();
461-
}
462-
// Always copy with the GLib allocator
463459
let b = self.as_bytes_with_nul();
460+
if self.len() < INLINE_LEN {
461+
let mut data = <[u8; INLINE_LEN]>::default();
462+
let b = self.as_bytes();
463+
unsafe { data.get_unchecked_mut(..b.len()) }.copy_from_slice(b);
464+
return GString(Inner::Inline {
465+
len: self.len() as u8,
466+
data,
467+
});
468+
}
464469
let inner = unsafe {
465470
let copy = ffi::g_strndup(b.as_ptr() as *const c_char, b.len());
466471
Inner::Foreign {
@@ -611,12 +616,17 @@ const INLINE_LEN: usize =
611616
/// The constructors beginning with `from_utf8` `and `from_string` can also be used to further
612617
/// control how interior nul-bytes are handled.
613618
pub struct GString(Inner);
619+
614620
enum Inner {
615-
Native(Option<Box<str>>),
621+
Native(Box<str>),
616622
Foreign {
617623
ptr: ptr::NonNull<c_char>,
618624
len: usize,
619625
},
626+
Inline {
627+
len: u8,
628+
data: [u8; INLINE_LEN],
629+
},
620630
}
621631

622632
unsafe impl Send for GString {}
@@ -629,7 +639,10 @@ impl GString {
629639
/// Does not allocate.
630640
#[inline]
631641
pub fn new() -> Self {
632-
Self(Inner::Native(None))
642+
Self(Inner::Inline {
643+
len: 0,
644+
data: Default::default(),
645+
})
633646
}
634647
// rustdoc-stripper-ignore-next
635648
/// Formats an [`Arguments`](std::fmt::Arguments) into a [`GString`].
@@ -693,11 +706,11 @@ impl GString {
693706
#[inline]
694707
pub unsafe fn from_utf8_unchecked(mut v: Vec<u8>) -> Self {
695708
if v.is_empty() {
696-
Self(Inner::Native(None))
709+
Self::new()
697710
} else {
698711
v.reserve_exact(1);
699712
v.push(0);
700-
Self(Inner::Native(Some(String::from_utf8_unchecked(v).into())))
713+
Self(Inner::Native(String::from_utf8_unchecked(v).into()))
701714
}
702715
}
703716
// rustdoc-stripper-ignore-next
@@ -713,9 +726,9 @@ impl GString {
713726
return Err(GStringNoTrailingNulError(s.into_bytes()).into());
714727
}
715728
if s.len() == 1 {
716-
Ok(Self(Inner::Native(None)))
729+
Ok(Self::new())
717730
} else {
718-
Ok(Self(Inner::Native(Some(s.into()))))
731+
Ok(Self(Inner::Native(s.into())))
719732
}
720733
}
721734
// rustdoc-stripper-ignore-next
@@ -750,9 +763,9 @@ impl GString {
750763
String::from_utf8_unchecked(v)
751764
};
752765
if s.len() == 1 {
753-
Self(Inner::Native(None))
766+
Self::new()
754767
} else {
755-
Self(Inner::Native(Some(s.into())))
768+
Self(Inner::Native(s.into()))
756769
}
757770
}
758771
// rustdoc-stripper-ignore-next
@@ -768,14 +781,14 @@ impl GString {
768781
return Err(GStringNoTrailingNulError(bytes).into());
769782
};
770783
if nul_pos == 0 {
771-
Ok(Self(Inner::Native(None)))
784+
Ok(Self::new())
772785
} else {
773786
if let Err(e) = std::str::from_utf8(unsafe { bytes.get_unchecked(..nul_pos) }) {
774787
return Err(GStringUtf8Error(bytes, e).into());
775788
}
776789
bytes.truncate(nul_pos + 1);
777790
let s = unsafe { String::from_utf8_unchecked(bytes) };
778-
Ok(Self(Inner::Native(Some(s.into()))))
791+
Ok(Self(Inner::Native(s.into())))
779792
}
780793
}
781794
// rustdoc-stripper-ignore-next
@@ -799,11 +812,11 @@ impl GString {
799812
#[inline]
800813
pub fn from_string_unchecked(mut s: String) -> Self {
801814
if s.is_empty() {
802-
Self(Inner::Native(None))
815+
Self::new()
803816
} else {
804817
s.reserve_exact(1);
805818
s.push('\0');
806-
Self(Inner::Native(Some(s.into())))
819+
Self(Inner::Native(s.into()))
807820
}
808821
}
809822
// rustdoc-stripper-ignore-next
@@ -838,9 +851,9 @@ impl GString {
838851
pub fn as_str(&self) -> &str {
839852
unsafe {
840853
let (ptr, len) = match self.0 {
841-
Inner::Native(None) => (ptr::null(), 0),
842-
Inner::Native(Some(ref s)) => (s.as_ptr() as *const u8, s.len() - 1),
854+
Inner::Native(ref s) => (s.as_ptr() as *const u8, s.len() - 1),
843855
Inner::Foreign { ptr, len } => (ptr.as_ptr() as *const u8, len),
856+
Inner::Inline { len, ref data } => (data.as_ptr(), len as usize),
844857
};
845858
if len == 0 {
846859
""
@@ -856,12 +869,12 @@ impl GString {
856869
#[inline]
857870
pub fn as_gstr(&self) -> &GStr {
858871
let bytes = match self.0 {
859-
Inner::Native(None) => return <&GStr>::default(),
860-
Inner::Native(Some(ref s)) => s.as_bytes(),
872+
Inner::Native(ref s) => s.as_bytes(),
861873
Inner::Foreign { len, .. } if len == 0 => &[0],
862874
Inner::Foreign { ptr, len } => unsafe {
863875
slice::from_raw_parts(ptr.as_ptr() as *const _, len + 1)
864876
},
877+
Inner::Inline { len, ref data } => unsafe { data.get_unchecked(..len as usize + 1) },
865878
};
866879
unsafe { GStr::from_utf8_with_nul_unchecked(bytes) }
867880
}
@@ -871,9 +884,9 @@ impl GString {
871884
#[inline]
872885
pub fn as_ptr(&self) -> *const c_char {
873886
match self.0 {
874-
Inner::Native(None) => <&GStr>::default().as_ptr(),
875-
Inner::Native(Some(ref s)) => s.as_ptr() as *const _,
887+
Inner::Native(ref s) => s.as_ptr() as *const _,
876888
Inner::Foreign { ptr, .. } => ptr.as_ptr(),
889+
Inner::Inline { ref data, .. } => data.as_ptr() as *const _,
877890
}
878891
}
879892

@@ -883,34 +896,34 @@ impl GString {
883896
/// The returned buffer is not guaranteed to contain a trailing nul-byte.
884897
pub fn into_bytes(mut self) -> Vec<u8> {
885898
match &mut self.0 {
886-
Inner::Native(s) => match s.take() {
887-
None => Vec::new(),
888-
Some(s) => {
889-
let mut s = String::from(s);
890-
let _nul = s.pop();
891-
debug_assert_eq!(_nul, Some('\0'));
892-
s.into_bytes()
893-
}
894-
},
899+
Inner::Native(s) => {
900+
let mut s = String::from(mem::replace(s, "".into()));
901+
let _nul = s.pop();
902+
debug_assert_eq!(_nul, Some('\0'));
903+
s.into_bytes()
904+
}
895905
Inner::Foreign { ptr, len } => {
896906
let bytes = unsafe { slice::from_raw_parts(ptr.as_ptr() as *const u8, *len - 1) };
897907
bytes.to_owned()
898908
}
909+
Inner::Inline { len, data } => {
910+
unsafe { data.get_unchecked(..*len as usize) }.to_owned()
911+
}
899912
}
900913
}
901914

902915
// rustdoc-stripper-ignore-next
903916
/// Consumes the `GString` and returns the underlying byte buffer, with trailing nul-byte.
904917
pub fn into_bytes_with_nul(mut self) -> Vec<u8> {
905918
match &mut self.0 {
906-
Inner::Native(s) => match s.take() {
907-
None => vec![0u8],
908-
Some(s) => str::into_boxed_bytes(s).into(),
909-
},
919+
Inner::Native(s) => str::into_boxed_bytes(mem::replace(s, "".into())).into(),
910920
Inner::Foreign { ptr, len } => {
911921
let bytes = unsafe { slice::from_raw_parts(ptr.as_ptr() as *const u8, *len) };
912922
bytes.to_owned()
913923
}
924+
Inner::Inline { len, data } => {
925+
unsafe { data.get_unchecked(..*len as usize + 1) }.to_owned()
926+
}
914927
}
915928
}
916929
}
@@ -1042,12 +1055,14 @@ impl IntoGlibPtr<*mut c_char> for GString {
10421055
/// Transform into a nul-terminated raw C string pointer.
10431056
unsafe fn into_glib_ptr(self) -> *mut c_char {
10441057
match self.0 {
1045-
Inner::Native(None) => ffi::g_malloc0(1) as *mut _,
1046-
Inner::Native(Some(ref s)) => ffi::g_strndup(s.as_ptr() as *const _, s.len()),
1058+
Inner::Native(ref s) => ffi::g_strndup(s.as_ptr() as *const _, s.len()),
10471059
Inner::Foreign { ptr, .. } => {
10481060
let _s = mem::ManuallyDrop::new(self);
10491061
ptr.as_ptr()
10501062
}
1063+
Inner::Inline { len, ref data } => {
1064+
ffi::g_strndup(data.as_ptr() as *const _, len as usize)
1065+
}
10511066
}
10521067
}
10531068
}
@@ -1295,22 +1310,22 @@ impl From<GString> for String {
12951310
#[inline]
12961311
fn from(mut s: GString) -> Self {
12971312
match &mut s.0 {
1298-
Inner::Native(s) => match s.take() {
1299-
None => Self::default(),
1300-
Some(s) => {
1301-
// Moves the underlying string
1302-
let mut s = String::from(s);
1303-
let _nul = s.pop();
1304-
debug_assert_eq!(_nul, Some('\0'));
1305-
s
1306-
}
1307-
},
1313+
Inner::Native(s) => {
1314+
// Moves the underlying string
1315+
let mut s = String::from(mem::replace(s, "".into()));
1316+
let _nul = s.pop();
1317+
debug_assert_eq!(_nul, Some('\0'));
1318+
s
1319+
}
13081320
Inner::Foreign { len, .. } if *len == 0 => String::new(),
13091321
Inner::Foreign { ptr, len } => unsafe {
13101322
// Creates a copy
13111323
let slice = slice::from_raw_parts(ptr.as_ptr() as *const u8, *len);
13121324
std::str::from_utf8_unchecked(slice).into()
13131325
},
1326+
Inner::Inline { len, data } => unsafe {
1327+
std::str::from_utf8_unchecked(data.get_unchecked(..*len as usize)).to_owned()
1328+
},
13141329
}
13151330
}
13161331
}
@@ -1367,12 +1382,12 @@ impl From<String> for GString {
13671382
GStr::check_interior_nuls(&s).unwrap();
13681383
}
13691384
if s.is_empty() {
1370-
Self(Inner::Native(None))
1385+
Self::new()
13711386
} else {
13721387
s.reserve_exact(1);
13731388
s.push('\0');
13741389
// No check for valid UTF-8 here
1375-
Self(Inner::Native(Some(s.into())))
1390+
Self(Inner::Native(s.into()))
13761391
}
13771392
}
13781393
}
@@ -1408,8 +1423,14 @@ impl From<&str> for GString {
14081423
if cfg!(debug_assertions) {
14091424
GStr::check_interior_nuls(s).unwrap();
14101425
}
1411-
if s.is_empty() {
1412-
return Self::default();
1426+
if s.len() < INLINE_LEN {
1427+
let mut data = <[u8; INLINE_LEN]>::default();
1428+
let b = s.as_bytes();
1429+
unsafe { data.get_unchecked_mut(..b.len()) }.copy_from_slice(b);
1430+
return Self(Inner::Inline {
1431+
len: b.len() as u8,
1432+
data,
1433+
});
14131434
}
14141435
// Allocates with the GLib allocator
14151436
unsafe {
@@ -1428,7 +1449,7 @@ impl TryFrom<CString> for GString {
14281449
#[inline]
14291450
fn try_from(value: CString) -> Result<Self, Self::Error> {
14301451
if value.as_bytes().is_empty() {
1431-
Ok(Self(Inner::Native(None)))
1452+
Ok(Self::new())
14321453
} else {
14331454
// Moves the content of the CString
14341455
// Also check if it's valid UTF-8
@@ -1439,7 +1460,7 @@ impl TryFrom<CString> for GString {
14391460
err,
14401461
)
14411462
})?;
1442-
Ok(Self(Inner::Native(Some(s.into()))))
1463+
Ok(Self(Inner::Native(s.into())))
14431464
}
14441465
}
14451466
}
@@ -2104,4 +2125,15 @@ mod tests {
21042125
let s = gformat!("bla bla {} bla", 123);
21052126
assert_eq!(s, "bla bla 123 bla");
21062127
}
2128+
2129+
#[test]
2130+
fn layout() {
2131+
// ensure the inline variant is not wider than the other variants
2132+
enum NoInline {
2133+
_Native(Box<str>),
2134+
_Foreign(ptr::NonNull<c_char>, usize),
2135+
}
2136+
assert_eq!(mem::size_of::<GString>(), mem::size_of::<NoInline>());
2137+
assert_eq!(mem::size_of::<GString>(), mem::size_of::<String>());
2138+
}
21072139
}

0 commit comments

Comments
 (0)