Skip to content

Commit aad2210

Browse files
committed
glib: allow GString to store small inline strings
1 parent 442c4e0 commit aad2210

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

439439
#[inline]
440440
fn to_owned(&self) -> Self::Owned {
441-
if self.is_empty() {
442-
return GString::default();
443-
}
444-
// Always copy with the GLib allocator
445441
let b = self.as_bytes_with_nul();
442+
if self.len() < INLINE_LEN {
443+
let mut data = <[u8; INLINE_LEN]>::default();
444+
let b = self.as_bytes();
445+
unsafe { data.get_unchecked_mut(..b.len()) }.copy_from_slice(b);
446+
return GString(Inner::Inline {
447+
len: self.len() as u8,
448+
data,
449+
});
450+
}
446451
let inner = unsafe {
447452
let copy = ffi::g_strndup(b.as_ptr() as *const c_char, b.len());
448453
Inner::Foreign {
@@ -593,12 +598,17 @@ const INLINE_LEN: usize =
593598
/// The constructors beginning with `from_utf8` `and `from_string` can also be used to further
594599
/// control how interior nul-bytes are handled.
595600
pub struct GString(Inner);
601+
596602
enum Inner {
597-
Native(Option<Box<str>>),
603+
Native(Box<str>),
598604
Foreign {
599605
ptr: ptr::NonNull<c_char>,
600606
len: usize,
601607
},
608+
Inline {
609+
len: u8,
610+
data: [u8; INLINE_LEN],
611+
},
602612
}
603613

604614
unsafe impl Send for GString {}
@@ -611,7 +621,10 @@ impl GString {
611621
/// Does not allocate.
612622
#[inline]
613623
pub fn new() -> Self {
614-
Self(Inner::Native(None))
624+
Self(Inner::Inline {
625+
len: 0,
626+
data: Default::default(),
627+
})
615628
}
616629
// rustdoc-stripper-ignore-next
617630
/// Formats an [`Arguments`](std::fmt::Arguments) into a [`GString`].
@@ -675,11 +688,11 @@ impl GString {
675688
#[inline]
676689
pub unsafe fn from_utf8_unchecked(mut v: Vec<u8>) -> Self {
677690
if v.is_empty() {
678-
Self(Inner::Native(None))
691+
Self::new()
679692
} else {
680693
v.reserve_exact(1);
681694
v.push(0);
682-
Self(Inner::Native(Some(String::from_utf8_unchecked(v).into())))
695+
Self(Inner::Native(String::from_utf8_unchecked(v).into()))
683696
}
684697
}
685698
// rustdoc-stripper-ignore-next
@@ -695,9 +708,9 @@ impl GString {
695708
return Err(GStringNoTrailingNulError(s.into_bytes()).into());
696709
}
697710
if s.len() == 1 {
698-
Ok(Self(Inner::Native(None)))
711+
Ok(Self::new())
699712
} else {
700-
Ok(Self(Inner::Native(Some(s.into()))))
713+
Ok(Self(Inner::Native(s.into())))
701714
}
702715
}
703716
// rustdoc-stripper-ignore-next
@@ -732,9 +745,9 @@ impl GString {
732745
String::from_utf8_unchecked(v)
733746
};
734747
if s.len() == 1 {
735-
Self(Inner::Native(None))
748+
Self::new()
736749
} else {
737-
Self(Inner::Native(Some(s.into())))
750+
Self(Inner::Native(s.into()))
738751
}
739752
}
740753
// rustdoc-stripper-ignore-next
@@ -750,14 +763,14 @@ impl GString {
750763
return Err(GStringNoTrailingNulError(bytes).into());
751764
};
752765
if nul_pos == 0 {
753-
Ok(Self(Inner::Native(None)))
766+
Ok(Self::new())
754767
} else {
755768
if let Err(e) = std::str::from_utf8(unsafe { bytes.get_unchecked(..nul_pos) }) {
756769
return Err(GStringUtf8Error(bytes, e).into());
757770
}
758771
bytes.truncate(nul_pos + 1);
759772
let s = unsafe { String::from_utf8_unchecked(bytes) };
760-
Ok(Self(Inner::Native(Some(s.into()))))
773+
Ok(Self(Inner::Native(s.into())))
761774
}
762775
}
763776
// rustdoc-stripper-ignore-next
@@ -781,11 +794,11 @@ impl GString {
781794
#[inline]
782795
pub fn from_string_unchecked(mut s: String) -> Self {
783796
if s.is_empty() {
784-
Self(Inner::Native(None))
797+
Self::new()
785798
} else {
786799
s.reserve_exact(1);
787800
s.push('\0');
788-
Self(Inner::Native(Some(s.into())))
801+
Self(Inner::Native(s.into()))
789802
}
790803
}
791804
// rustdoc-stripper-ignore-next
@@ -794,9 +807,9 @@ impl GString {
794807
pub fn as_str(&self) -> &str {
795808
unsafe {
796809
let (ptr, len) = match self.0 {
797-
Inner::Native(None) => (ptr::null(), 0),
798-
Inner::Native(Some(ref s)) => (s.as_ptr() as *const u8, s.len() - 1),
810+
Inner::Native(ref s) => (s.as_ptr() as *const u8, s.len() - 1),
799811
Inner::Foreign { ptr, len } => (ptr.as_ptr() as *const u8, len),
812+
Inner::Inline { len, ref data } => (data.as_ptr(), len as usize),
800813
};
801814
if len == 0 {
802815
""
@@ -812,12 +825,12 @@ impl GString {
812825
#[inline]
813826
pub fn as_gstr(&self) -> &GStr {
814827
let bytes = match self.0 {
815-
Inner::Native(None) => return <&GStr>::default(),
816-
Inner::Native(Some(ref s)) => s.as_bytes(),
828+
Inner::Native(ref s) => s.as_bytes(),
817829
Inner::Foreign { len, .. } if len == 0 => &[0],
818830
Inner::Foreign { ptr, len } => unsafe {
819831
slice::from_raw_parts(ptr.as_ptr() as *const _, len + 1)
820832
},
833+
Inner::Inline { len, ref data } => unsafe { data.get_unchecked(..len as usize + 1) },
821834
};
822835
unsafe { GStr::from_utf8_with_nul_unchecked(bytes) }
823836
}
@@ -827,9 +840,9 @@ impl GString {
827840
#[inline]
828841
pub fn as_ptr(&self) -> *const c_char {
829842
match self.0 {
830-
Inner::Native(None) => <&GStr>::default().as_ptr(),
831-
Inner::Native(Some(ref s)) => s.as_ptr() as *const _,
843+
Inner::Native(ref s) => s.as_ptr() as *const _,
832844
Inner::Foreign { ptr, .. } => ptr.as_ptr(),
845+
Inner::Inline { ref data, .. } => data.as_ptr() as *const _,
833846
}
834847
}
835848

@@ -839,34 +852,34 @@ impl GString {
839852
/// The returned buffer is not guaranteed to contain a trailing nul-byte.
840853
pub fn into_bytes(mut self) -> Vec<u8> {
841854
match &mut self.0 {
842-
Inner::Native(s) => match s.take() {
843-
None => Vec::new(),
844-
Some(s) => {
845-
let mut s = String::from(s);
846-
let _nul = s.pop();
847-
debug_assert_eq!(_nul, Some('\0'));
848-
s.into_bytes()
849-
}
850-
},
855+
Inner::Native(s) => {
856+
let mut s = String::from(mem::replace(s, "".into()));
857+
let _nul = s.pop();
858+
debug_assert_eq!(_nul, Some('\0'));
859+
s.into_bytes()
860+
}
851861
Inner::Foreign { ptr, len } => {
852862
let bytes = unsafe { slice::from_raw_parts(ptr.as_ptr() as *const u8, *len - 1) };
853863
bytes.to_owned()
854864
}
865+
Inner::Inline { len, data } => {
866+
unsafe { data.get_unchecked(..*len as usize) }.to_owned()
867+
}
855868
}
856869
}
857870

858871
// rustdoc-stripper-ignore-next
859872
/// Consumes the `GString` and returns the underlying byte buffer, with trailing nul-byte.
860873
pub fn into_bytes_with_nul(mut self) -> Vec<u8> {
861874
match &mut self.0 {
862-
Inner::Native(s) => match s.take() {
863-
None => vec![0u8],
864-
Some(s) => str::into_boxed_bytes(s).into(),
865-
},
875+
Inner::Native(s) => str::into_boxed_bytes(mem::replace(s, "".into())).into(),
866876
Inner::Foreign { ptr, len } => {
867877
let bytes = unsafe { slice::from_raw_parts(ptr.as_ptr() as *const u8, *len) };
868878
bytes.to_owned()
869879
}
880+
Inner::Inline { len, data } => {
881+
unsafe { data.get_unchecked(..*len as usize + 1) }.to_owned()
882+
}
870883
}
871884
}
872885
}
@@ -998,12 +1011,14 @@ impl IntoGlibPtr<*mut c_char> for GString {
9981011
/// Transform into a nul-terminated raw C string pointer.
9991012
unsafe fn into_glib_ptr(self) -> *mut c_char {
10001013
match self.0 {
1001-
Inner::Native(None) => ffi::g_malloc0(1) as *mut _,
1002-
Inner::Native(Some(ref s)) => ffi::g_strndup(s.as_ptr() as *const _, s.len()),
1014+
Inner::Native(ref s) => ffi::g_strndup(s.as_ptr() as *const _, s.len()),
10031015
Inner::Foreign { ptr, .. } => {
10041016
let _s = mem::ManuallyDrop::new(self);
10051017
ptr.as_ptr()
10061018
}
1019+
Inner::Inline { len, ref data } => {
1020+
ffi::g_strndup(data.as_ptr() as *const _, len as usize)
1021+
}
10071022
}
10081023
}
10091024
}
@@ -1251,22 +1266,22 @@ impl From<GString> for String {
12511266
#[inline]
12521267
fn from(mut s: GString) -> Self {
12531268
match &mut s.0 {
1254-
Inner::Native(s) => match s.take() {
1255-
None => Self::default(),
1256-
Some(s) => {
1257-
// Moves the underlying string
1258-
let mut s = String::from(s);
1259-
let _nul = s.pop();
1260-
debug_assert_eq!(_nul, Some('\0'));
1261-
s
1262-
}
1263-
},
1269+
Inner::Native(s) => {
1270+
// Moves the underlying string
1271+
let mut s = String::from(mem::replace(s, "".into()));
1272+
let _nul = s.pop();
1273+
debug_assert_eq!(_nul, Some('\0'));
1274+
s
1275+
}
12641276
Inner::Foreign { len, .. } if *len == 0 => String::new(),
12651277
Inner::Foreign { ptr, len } => unsafe {
12661278
// Creates a copy
12671279
let slice = slice::from_raw_parts(ptr.as_ptr() as *const u8, *len);
12681280
std::str::from_utf8_unchecked(slice).into()
12691281
},
1282+
Inner::Inline { len, data } => unsafe {
1283+
std::str::from_utf8_unchecked(data.get_unchecked(..*len as usize)).to_owned()
1284+
},
12701285
}
12711286
}
12721287
}
@@ -1323,12 +1338,12 @@ impl From<String> for GString {
13231338
GStr::check_interior_nuls(&s).unwrap();
13241339
}
13251340
if s.is_empty() {
1326-
Self(Inner::Native(None))
1341+
Self::new()
13271342
} else {
13281343
s.reserve_exact(1);
13291344
s.push('\0');
13301345
// No check for valid UTF-8 here
1331-
Self(Inner::Native(Some(s.into())))
1346+
Self(Inner::Native(s.into()))
13321347
}
13331348
}
13341349
}
@@ -1364,8 +1379,14 @@ impl From<&str> for GString {
13641379
if cfg!(debug_assertions) {
13651380
GStr::check_interior_nuls(s).unwrap();
13661381
}
1367-
if s.is_empty() {
1368-
return Self::default();
1382+
if s.len() < INLINE_LEN {
1383+
let mut data = <[u8; INLINE_LEN]>::default();
1384+
let b = s.as_bytes();
1385+
unsafe { data.get_unchecked_mut(..b.len()) }.copy_from_slice(b);
1386+
return Self(Inner::Inline {
1387+
len: b.len() as u8,
1388+
data,
1389+
});
13691390
}
13701391
// Allocates with the GLib allocator
13711392
unsafe {
@@ -1384,7 +1405,7 @@ impl TryFrom<CString> for GString {
13841405
#[inline]
13851406
fn try_from(value: CString) -> Result<Self, Self::Error> {
13861407
if value.as_bytes().is_empty() {
1387-
Ok(Self(Inner::Native(None)))
1408+
Ok(Self::new())
13881409
} else {
13891410
// Moves the content of the CString
13901411
// Also check if it's valid UTF-8
@@ -1395,7 +1416,7 @@ impl TryFrom<CString> for GString {
13951416
err,
13961417
)
13971418
})?;
1398-
Ok(Self(Inner::Native(Some(s.into()))))
1419+
Ok(Self(Inner::Native(s.into())))
13991420
}
14001421
}
14011422
}
@@ -2002,4 +2023,15 @@ mod tests {
20022023
let s = gformat!("bla bla {} bla", 123);
20032024
assert_eq!(s, "bla bla 123 bla");
20042025
}
2026+
2027+
#[test]
2028+
fn layout() {
2029+
// ensure the inline variant is not wider than the other variants
2030+
enum NoInline {
2031+
_Native(Box<str>),
2032+
_Foreign(ptr::NonNull<c_char>, usize),
2033+
}
2034+
assert_eq!(mem::size_of::<GString>(), mem::size_of::<NoInline>());
2035+
assert_eq!(mem::size_of::<GString>(), mem::size_of::<String>());
2036+
}
20052037
}

0 commit comments

Comments
 (0)