Skip to content

Commit 4bee802

Browse files
committed
glib: allow GString to store small inline strings
1 parent 7895f19 commit 4bee802

File tree

1 file changed

+84
-53
lines changed

1 file changed

+84
-53
lines changed

glib/src/gstring.rs

Lines changed: 84 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 {
@@ -594,12 +599,17 @@ const INLINE_LEN: usize =
594599
/// control how interior nul-bytes are handled.
595600
#[repr(transparent)]
596601
pub struct GString(Inner);
602+
597603
enum Inner {
598-
Native(Option<Box<str>>),
604+
Native(Box<str>),
599605
Foreign {
600606
ptr: ptr::NonNull<c_char>,
601607
len: usize,
602608
},
609+
Inline {
610+
len: u8,
611+
data: [u8; INLINE_LEN],
612+
},
603613
}
604614

605615
unsafe impl Send for GString {}
@@ -612,7 +622,10 @@ impl GString {
612622
/// Does not allocate.
613623
#[inline]
614624
pub fn new() -> Self {
615-
Self(Inner::Native(None))
625+
Self(Inner::Inline {
626+
len: 0,
627+
data: Default::default(),
628+
})
616629
}
617630
// rustdoc-stripper-ignore-next
618631
/// Formats an [`Arguments`](std::fmt::Arguments) into a [`GString`].
@@ -676,11 +689,11 @@ impl GString {
676689
#[inline]
677690
pub unsafe fn from_utf8_unchecked(mut v: Vec<u8>) -> Self {
678691
if v.is_empty() {
679-
Self(Inner::Native(None))
692+
Self::new()
680693
} else {
681694
v.reserve_exact(1);
682695
v.push(0);
683-
Self(Inner::Native(Some(String::from_utf8_unchecked(v).into())))
696+
Self(Inner::Native(String::from_utf8_unchecked(v).into()))
684697
}
685698
}
686699
// rustdoc-stripper-ignore-next
@@ -696,9 +709,9 @@ impl GString {
696709
return Err(GStringNoTrailingNulError(s.into_bytes()).into());
697710
}
698711
if s.len() == 1 {
699-
Ok(Self(Inner::Native(None)))
712+
Ok(Self::new())
700713
} else {
701-
Ok(Self(Inner::Native(Some(s.into()))))
714+
Ok(Self(Inner::Native(s.into())))
702715
}
703716
}
704717
// rustdoc-stripper-ignore-next
@@ -733,9 +746,9 @@ impl GString {
733746
String::from_utf8_unchecked(v)
734747
};
735748
if s.len() == 1 {
736-
Self(Inner::Native(None))
749+
Self::new()
737750
} else {
738-
Self(Inner::Native(Some(s.into())))
751+
Self(Inner::Native(s.into()))
739752
}
740753
}
741754
// rustdoc-stripper-ignore-next
@@ -751,14 +764,14 @@ impl GString {
751764
return Err(GStringNoTrailingNulError(bytes).into());
752765
};
753766
if nul_pos == 0 {
754-
Ok(Self(Inner::Native(None)))
767+
Ok(Self::new())
755768
} else {
756769
if let Err(e) = std::str::from_utf8(unsafe { bytes.get_unchecked(..nul_pos) }) {
757770
return Err(GStringUtf8Error(bytes, e).into());
758771
}
759772
bytes.truncate(nul_pos + 1);
760773
let s = unsafe { String::from_utf8_unchecked(bytes) };
761-
Ok(Self(Inner::Native(Some(s.into()))))
774+
Ok(Self(Inner::Native(s.into())))
762775
}
763776
}
764777
// rustdoc-stripper-ignore-next
@@ -782,11 +795,11 @@ impl GString {
782795
#[inline]
783796
pub fn from_string_unchecked(mut s: String) -> Self {
784797
if s.is_empty() {
785-
Self(Inner::Native(None))
798+
Self::new()
786799
} else {
787800
s.reserve_exact(1);
788801
s.push('\0');
789-
Self(Inner::Native(Some(s.into())))
802+
Self(Inner::Native(s.into()))
790803
}
791804
}
792805
// rustdoc-stripper-ignore-next
@@ -795,9 +808,9 @@ impl GString {
795808
pub fn as_str(&self) -> &str {
796809
unsafe {
797810
let (ptr, len) = match self.0 {
798-
Inner::Native(None) => (ptr::null(), 0),
799-
Inner::Native(Some(ref s)) => (s.as_ptr() as *const u8, s.len() - 1),
811+
Inner::Native(ref s) => (s.as_ptr() as *const u8, s.len() - 1),
800812
Inner::Foreign { ptr, len } => (ptr.as_ptr() as *const u8, len),
813+
Inner::Inline { len, ref data } => (data.as_ptr(), len as usize),
801814
};
802815
if len == 0 {
803816
""
@@ -813,12 +826,12 @@ impl GString {
813826
#[inline]
814827
pub fn as_gstr(&self) -> &GStr {
815828
let bytes = match self.0 {
816-
Inner::Native(None) => return <&GStr>::default(),
817-
Inner::Native(Some(ref s)) => s.as_bytes(),
829+
Inner::Native(ref s) => s.as_bytes(),
818830
Inner::Foreign { len, .. } if len == 0 => &[0],
819831
Inner::Foreign { ptr, len } => unsafe {
820832
slice::from_raw_parts(ptr.as_ptr() as *const _, len + 1)
821833
},
834+
Inner::Inline { len, ref data } => unsafe { data.get_unchecked(..len as usize + 1) },
822835
};
823836
unsafe { GStr::from_utf8_with_nul_unchecked(bytes) }
824837
}
@@ -828,9 +841,9 @@ impl GString {
828841
#[inline]
829842
pub fn as_ptr(&self) -> *const c_char {
830843
match self.0 {
831-
Inner::Native(None) => <&GStr>::default().as_ptr(),
832-
Inner::Native(Some(ref s)) => s.as_ptr() as *const _,
844+
Inner::Native(ref s) => s.as_ptr() as *const _,
833845
Inner::Foreign { ptr, .. } => ptr.as_ptr(),
846+
Inner::Inline { ref data, .. } => data.as_ptr() as *const _,
834847
}
835848
}
836849

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

859872
// rustdoc-stripper-ignore-next
860873
/// Consumes the `GString` and returns the underlying byte buffer, with trailing nul-byte.
861874
pub fn into_bytes_with_nul(mut self) -> Vec<u8> {
862875
match &mut self.0 {
863-
Inner::Native(s) => match s.take() {
864-
None => vec![0u8],
865-
Some(s) => str::into_boxed_bytes(s).into(),
866-
},
876+
Inner::Native(s) => str::into_boxed_bytes(mem::replace(s, "".into())).into(),
867877
Inner::Foreign { ptr, len } => {
868878
let bytes = unsafe { slice::from_raw_parts(ptr.as_ptr() as *const u8, *len) };
869879
bytes.to_owned()
870880
}
881+
Inner::Inline { len, data } => {
882+
unsafe { data.get_unchecked(..*len as usize + 1) }.to_owned()
883+
}
871884
}
872885
}
873886
}
@@ -999,12 +1012,14 @@ impl IntoGlibPtr<*mut c_char> for GString {
9991012
/// Transform into a nul-terminated raw C string pointer.
10001013
unsafe fn into_glib_ptr(self) -> *mut c_char {
10011014
match self.0 {
1002-
Inner::Native(None) => ffi::g_malloc0(1) as *mut _,
1003-
Inner::Native(Some(ref s)) => ffi::g_strndup(s.as_ptr() as *const _, s.len()),
1015+
Inner::Native(ref s) => ffi::g_strndup(s.as_ptr() as *const _, s.len()),
10041016
Inner::Foreign { ptr, .. } => {
10051017
let _s = mem::ManuallyDrop::new(self);
10061018
ptr.as_ptr()
10071019
}
1020+
Inner::Inline { len, ref data } => {
1021+
ffi::g_strndup(data.as_ptr() as *const _, len as usize)
1022+
}
10081023
}
10091024
}
10101025
}
@@ -1252,22 +1267,22 @@ impl From<GString> for String {
12521267
#[inline]
12531268
fn from(mut s: GString) -> Self {
12541269
match &mut s.0 {
1255-
Inner::Native(s) => match s.take() {
1256-
None => Self::default(),
1257-
Some(s) => {
1258-
// Moves the underlying string
1259-
let mut s = String::from(s);
1260-
let _nul = s.pop();
1261-
debug_assert_eq!(_nul, Some('\0'));
1262-
s
1263-
}
1264-
},
1270+
Inner::Native(s) => {
1271+
// Moves the underlying string
1272+
let mut s = String::from(mem::replace(s, "".into()));
1273+
let _nul = s.pop();
1274+
debug_assert_eq!(_nul, Some('\0'));
1275+
s
1276+
}
12651277
Inner::Foreign { len, .. } if *len == 0 => String::new(),
12661278
Inner::Foreign { ptr, len } => unsafe {
12671279
// Creates a copy
12681280
let slice = slice::from_raw_parts(ptr.as_ptr() as *const u8, *len);
12691281
std::str::from_utf8_unchecked(slice).into()
12701282
},
1283+
Inner::Inline { len, data } => unsafe {
1284+
std::str::from_utf8_unchecked(data.get_unchecked(..*len as usize)).to_owned()
1285+
},
12711286
}
12721287
}
12731288
}
@@ -1324,12 +1339,12 @@ impl From<String> for GString {
13241339
GStr::check_interior_nuls(&s).unwrap();
13251340
}
13261341
if s.is_empty() {
1327-
Self(Inner::Native(None))
1342+
Self::new()
13281343
} else {
13291344
s.reserve_exact(1);
13301345
s.push('\0');
13311346
// No check for valid UTF-8 here
1332-
Self(Inner::Native(Some(s.into())))
1347+
Self(Inner::Native(s.into()))
13331348
}
13341349
}
13351350
}
@@ -1365,8 +1380,14 @@ impl From<&str> for GString {
13651380
if cfg!(debug_assertions) {
13661381
GStr::check_interior_nuls(s).unwrap();
13671382
}
1368-
if s.is_empty() {
1369-
return Self::default();
1383+
if s.len() < INLINE_LEN {
1384+
let mut data = <[u8; INLINE_LEN]>::default();
1385+
let b = s.as_bytes();
1386+
unsafe { data.get_unchecked_mut(..b.len()) }.copy_from_slice(b);
1387+
return Self(Inner::Inline {
1388+
len: b.len() as u8,
1389+
data,
1390+
});
13701391
}
13711392
// Allocates with the GLib allocator
13721393
unsafe {
@@ -1385,7 +1406,7 @@ impl TryFrom<CString> for GString {
13851406
#[inline]
13861407
fn try_from(value: CString) -> Result<Self, Self::Error> {
13871408
if value.as_bytes().is_empty() {
1388-
Ok(Self(Inner::Native(None)))
1409+
Ok(Self::new())
13891410
} else {
13901411
// Moves the content of the CString
13911412
// Also check if it's valid UTF-8
@@ -1396,7 +1417,7 @@ impl TryFrom<CString> for GString {
13961417
err,
13971418
)
13981419
})?;
1399-
Ok(Self(Inner::Native(Some(s.into()))))
1420+
Ok(Self(Inner::Native(s.into())))
14001421
}
14011422
}
14021423
}
@@ -2003,4 +2024,14 @@ mod tests {
20032024
let s = gformat!("bla bla {} bla", 123);
20042025
assert_eq!(s, "bla bla 123 bla");
20052026
}
2027+
2028+
#[test]
2029+
fn layout() {
2030+
// ensure the inline variant is not wider than the other variants
2031+
enum NoInline {
2032+
_Native(Box<str>),
2033+
_Foreign(ptr::NonNull<c_char>, usize),
2034+
}
2035+
assert_eq!(mem::size_of::<GString>(), mem::size_of::<NoInline>());
2036+
}
20062037
}

0 commit comments

Comments
 (0)