Skip to content

Commit e4f2100

Browse files
committed
glib: allow GString to store small inline strings
1 parent 6d15bed commit e4f2100

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
@@ -454,11 +454,16 @@ impl ToOwned for GStr {
454454

455455
#[inline]
456456
fn to_owned(&self) -> Self::Owned {
457-
if self.is_empty() {
458-
return GString::default();
459-
}
460-
// Always copy with the GLib allocator
461457
let b = self.as_bytes_with_nul();
458+
if self.len() < INLINE_LEN {
459+
let mut data = <[u8; INLINE_LEN]>::default();
460+
let b = self.as_bytes();
461+
unsafe { data.get_unchecked_mut(..b.len()) }.copy_from_slice(b);
462+
return GString(Inner::Inline {
463+
len: self.len() as u8,
464+
data,
465+
});
466+
}
462467
let inner = unsafe {
463468
let copy = ffi::g_strndup(b.as_ptr() as *const c_char, b.len());
464469
Inner::Foreign {
@@ -609,12 +614,17 @@ const INLINE_LEN: usize =
609614
/// The constructors beginning with `from_utf8` `and `from_string` can also be used to further
610615
/// control how interior nul-bytes are handled.
611616
pub struct GString(Inner);
617+
612618
enum Inner {
613-
Native(Option<Box<str>>),
619+
Native(Box<str>),
614620
Foreign {
615621
ptr: ptr::NonNull<c_char>,
616622
len: usize,
617623
},
624+
Inline {
625+
len: u8,
626+
data: [u8; INLINE_LEN],
627+
},
618628
}
619629

620630
unsafe impl Send for GString {}
@@ -627,7 +637,10 @@ impl GString {
627637
/// Does not allocate.
628638
#[inline]
629639
pub fn new() -> Self {
630-
Self(Inner::Native(None))
640+
Self(Inner::Inline {
641+
len: 0,
642+
data: Default::default(),
643+
})
631644
}
632645
// rustdoc-stripper-ignore-next
633646
/// Formats an [`Arguments`](std::fmt::Arguments) into a [`GString`].
@@ -691,11 +704,11 @@ impl GString {
691704
#[inline]
692705
pub unsafe fn from_utf8_unchecked(mut v: Vec<u8>) -> Self {
693706
if v.is_empty() {
694-
Self(Inner::Native(None))
707+
Self::new()
695708
} else {
696709
v.reserve_exact(1);
697710
v.push(0);
698-
Self(Inner::Native(Some(String::from_utf8_unchecked(v).into())))
711+
Self(Inner::Native(String::from_utf8_unchecked(v).into()))
699712
}
700713
}
701714
// rustdoc-stripper-ignore-next
@@ -711,9 +724,9 @@ impl GString {
711724
return Err(GStringNoTrailingNulError(s.into_bytes()).into());
712725
}
713726
if s.len() == 1 {
714-
Ok(Self(Inner::Native(None)))
727+
Ok(Self::new())
715728
} else {
716-
Ok(Self(Inner::Native(Some(s.into()))))
729+
Ok(Self(Inner::Native(s.into())))
717730
}
718731
}
719732
// rustdoc-stripper-ignore-next
@@ -748,9 +761,9 @@ impl GString {
748761
String::from_utf8_unchecked(v)
749762
};
750763
if s.len() == 1 {
751-
Self(Inner::Native(None))
764+
Self::new()
752765
} else {
753-
Self(Inner::Native(Some(s.into())))
766+
Self(Inner::Native(s.into()))
754767
}
755768
}
756769
// rustdoc-stripper-ignore-next
@@ -766,14 +779,14 @@ impl GString {
766779
return Err(GStringNoTrailingNulError(bytes).into());
767780
};
768781
if nul_pos == 0 {
769-
Ok(Self(Inner::Native(None)))
782+
Ok(Self::new())
770783
} else {
771784
if let Err(e) = std::str::from_utf8(unsafe { bytes.get_unchecked(..nul_pos) }) {
772785
return Err(GStringUtf8Error(bytes, e).into());
773786
}
774787
bytes.truncate(nul_pos + 1);
775788
let s = unsafe { String::from_utf8_unchecked(bytes) };
776-
Ok(Self(Inner::Native(Some(s.into()))))
789+
Ok(Self(Inner::Native(s.into())))
777790
}
778791
}
779792
// rustdoc-stripper-ignore-next
@@ -797,11 +810,11 @@ impl GString {
797810
#[inline]
798811
pub fn from_string_unchecked(mut s: String) -> Self {
799812
if s.is_empty() {
800-
Self(Inner::Native(None))
813+
Self::new()
801814
} else {
802815
s.reserve_exact(1);
803816
s.push('\0');
804-
Self(Inner::Native(Some(s.into())))
817+
Self(Inner::Native(s.into()))
805818
}
806819
}
807820
// rustdoc-stripper-ignore-next
@@ -836,9 +849,9 @@ impl GString {
836849
pub fn as_str(&self) -> &str {
837850
unsafe {
838851
let (ptr, len) = match self.0 {
839-
Inner::Native(None) => (ptr::null(), 0),
840-
Inner::Native(Some(ref s)) => (s.as_ptr() as *const u8, s.len() - 1),
852+
Inner::Native(ref s) => (s.as_ptr() as *const u8, s.len() - 1),
841853
Inner::Foreign { ptr, len } => (ptr.as_ptr() as *const u8, len),
854+
Inner::Inline { len, ref data } => (data.as_ptr(), len as usize),
842855
};
843856
if len == 0 {
844857
""
@@ -854,12 +867,12 @@ impl GString {
854867
#[inline]
855868
pub fn as_gstr(&self) -> &GStr {
856869
let bytes = match self.0 {
857-
Inner::Native(None) => return <&GStr>::default(),
858-
Inner::Native(Some(ref s)) => s.as_bytes(),
870+
Inner::Native(ref s) => s.as_bytes(),
859871
Inner::Foreign { len, .. } if len == 0 => &[0],
860872
Inner::Foreign { ptr, len } => unsafe {
861873
slice::from_raw_parts(ptr.as_ptr() as *const _, len + 1)
862874
},
875+
Inner::Inline { len, ref data } => unsafe { data.get_unchecked(..len as usize + 1) },
863876
};
864877
unsafe { GStr::from_utf8_with_nul_unchecked(bytes) }
865878
}
@@ -869,9 +882,9 @@ impl GString {
869882
#[inline]
870883
pub fn as_ptr(&self) -> *const c_char {
871884
match self.0 {
872-
Inner::Native(None) => <&GStr>::default().as_ptr(),
873-
Inner::Native(Some(ref s)) => s.as_ptr() as *const _,
885+
Inner::Native(ref s) => s.as_ptr() as *const _,
874886
Inner::Foreign { ptr, .. } => ptr.as_ptr(),
887+
Inner::Inline { ref data, .. } => data.as_ptr() as *const _,
875888
}
876889
}
877890

@@ -881,34 +894,34 @@ impl GString {
881894
/// The returned buffer is not guaranteed to contain a trailing nul-byte.
882895
pub fn into_bytes(mut self) -> Vec<u8> {
883896
match &mut self.0 {
884-
Inner::Native(s) => match s.take() {
885-
None => Vec::new(),
886-
Some(s) => {
887-
let mut s = String::from(s);
888-
let _nul = s.pop();
889-
debug_assert_eq!(_nul, Some('\0'));
890-
s.into_bytes()
891-
}
892-
},
897+
Inner::Native(s) => {
898+
let mut s = String::from(mem::replace(s, "".into()));
899+
let _nul = s.pop();
900+
debug_assert_eq!(_nul, Some('\0'));
901+
s.into_bytes()
902+
}
893903
Inner::Foreign { ptr, len } => {
894904
let bytes = unsafe { slice::from_raw_parts(ptr.as_ptr() as *const u8, *len - 1) };
895905
bytes.to_owned()
896906
}
907+
Inner::Inline { len, data } => {
908+
unsafe { data.get_unchecked(..*len as usize) }.to_owned()
909+
}
897910
}
898911
}
899912

900913
// rustdoc-stripper-ignore-next
901914
/// Consumes the `GString` and returns the underlying byte buffer, with trailing nul-byte.
902915
pub fn into_bytes_with_nul(mut self) -> Vec<u8> {
903916
match &mut self.0 {
904-
Inner::Native(s) => match s.take() {
905-
None => vec![0u8],
906-
Some(s) => str::into_boxed_bytes(s).into(),
907-
},
917+
Inner::Native(s) => str::into_boxed_bytes(mem::replace(s, "".into())).into(),
908918
Inner::Foreign { ptr, len } => {
909919
let bytes = unsafe { slice::from_raw_parts(ptr.as_ptr() as *const u8, *len) };
910920
bytes.to_owned()
911921
}
922+
Inner::Inline { len, data } => {
923+
unsafe { data.get_unchecked(..*len as usize + 1) }.to_owned()
924+
}
912925
}
913926
}
914927
}
@@ -1040,12 +1053,14 @@ impl IntoGlibPtr<*mut c_char> for GString {
10401053
/// Transform into a nul-terminated raw C string pointer.
10411054
unsafe fn into_glib_ptr(self) -> *mut c_char {
10421055
match self.0 {
1043-
Inner::Native(None) => ffi::g_malloc0(1) as *mut _,
1044-
Inner::Native(Some(ref s)) => ffi::g_strndup(s.as_ptr() as *const _, s.len()),
1056+
Inner::Native(ref s) => ffi::g_strndup(s.as_ptr() as *const _, s.len()),
10451057
Inner::Foreign { ptr, .. } => {
10461058
let _s = mem::ManuallyDrop::new(self);
10471059
ptr.as_ptr()
10481060
}
1061+
Inner::Inline { len, ref data } => {
1062+
ffi::g_strndup(data.as_ptr() as *const _, len as usize)
1063+
}
10491064
}
10501065
}
10511066
}
@@ -1293,22 +1308,22 @@ impl From<GString> for String {
12931308
#[inline]
12941309
fn from(mut s: GString) -> Self {
12951310
match &mut s.0 {
1296-
Inner::Native(s) => match s.take() {
1297-
None => Self::default(),
1298-
Some(s) => {
1299-
// Moves the underlying string
1300-
let mut s = String::from(s);
1301-
let _nul = s.pop();
1302-
debug_assert_eq!(_nul, Some('\0'));
1303-
s
1304-
}
1305-
},
1311+
Inner::Native(s) => {
1312+
// Moves the underlying string
1313+
let mut s = String::from(mem::replace(s, "".into()));
1314+
let _nul = s.pop();
1315+
debug_assert_eq!(_nul, Some('\0'));
1316+
s
1317+
}
13061318
Inner::Foreign { len, .. } if *len == 0 => String::new(),
13071319
Inner::Foreign { ptr, len } => unsafe {
13081320
// Creates a copy
13091321
let slice = slice::from_raw_parts(ptr.as_ptr() as *const u8, *len);
13101322
std::str::from_utf8_unchecked(slice).into()
13111323
},
1324+
Inner::Inline { len, data } => unsafe {
1325+
std::str::from_utf8_unchecked(data.get_unchecked(..*len as usize)).to_owned()
1326+
},
13121327
}
13131328
}
13141329
}
@@ -1365,12 +1380,12 @@ impl From<String> for GString {
13651380
GStr::check_interior_nuls(&s).unwrap();
13661381
}
13671382
if s.is_empty() {
1368-
Self(Inner::Native(None))
1383+
Self::new()
13691384
} else {
13701385
s.reserve_exact(1);
13711386
s.push('\0');
13721387
// No check for valid UTF-8 here
1373-
Self(Inner::Native(Some(s.into())))
1388+
Self(Inner::Native(s.into()))
13741389
}
13751390
}
13761391
}
@@ -1406,8 +1421,14 @@ impl From<&str> for GString {
14061421
if cfg!(debug_assertions) {
14071422
GStr::check_interior_nuls(s).unwrap();
14081423
}
1409-
if s.is_empty() {
1410-
return Self::default();
1424+
if s.len() < INLINE_LEN {
1425+
let mut data = <[u8; INLINE_LEN]>::default();
1426+
let b = s.as_bytes();
1427+
unsafe { data.get_unchecked_mut(..b.len()) }.copy_from_slice(b);
1428+
return Self(Inner::Inline {
1429+
len: b.len() as u8,
1430+
data,
1431+
});
14111432
}
14121433
// Allocates with the GLib allocator
14131434
unsafe {
@@ -1426,7 +1447,7 @@ impl TryFrom<CString> for GString {
14261447
#[inline]
14271448
fn try_from(value: CString) -> Result<Self, Self::Error> {
14281449
if value.as_bytes().is_empty() {
1429-
Ok(Self(Inner::Native(None)))
1450+
Ok(Self::new())
14301451
} else {
14311452
// Moves the content of the CString
14321453
// Also check if it's valid UTF-8
@@ -1437,7 +1458,7 @@ impl TryFrom<CString> for GString {
14371458
err,
14381459
)
14391460
})?;
1440-
Ok(Self(Inner::Native(Some(s.into()))))
1461+
Ok(Self(Inner::Native(s.into())))
14411462
}
14421463
}
14431464
}
@@ -2102,4 +2123,15 @@ mod tests {
21022123
let s = gformat!("bla bla {} bla", 123);
21032124
assert_eq!(s, "bla bla 123 bla");
21042125
}
2126+
2127+
#[test]
2128+
fn layout() {
2129+
// ensure the inline variant is not wider than the other variants
2130+
enum NoInline {
2131+
_Native(Box<str>),
2132+
_Foreign(ptr::NonNull<c_char>, usize),
2133+
}
2134+
assert_eq!(mem::size_of::<GString>(), mem::size_of::<NoInline>());
2135+
assert_eq!(mem::size_of::<GString>(), mem::size_of::<String>());
2136+
}
21052137
}

0 commit comments

Comments
 (0)