Skip to content

Commit c77cbfc

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

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`].
@@ -683,11 +696,11 @@ impl GString {
683696
#[inline]
684697
pub unsafe fn from_utf8_unchecked(mut v: Vec<u8>) -> Self {
685698
if v.is_empty() {
686-
Self(Inner::Native(None))
699+
Self::new()
687700
} else {
688701
v.reserve_exact(1);
689702
v.push(0);
690-
Self(Inner::Native(Some(String::from_utf8_unchecked(v).into())))
703+
Self(Inner::Native(String::from_utf8_unchecked(v).into()))
691704
}
692705
}
693706
// rustdoc-stripper-ignore-next
@@ -703,9 +716,9 @@ impl GString {
703716
return Err(GStringNoTrailingNulError(s.into_bytes()).into());
704717
}
705718
if s.len() == 1 {
706-
Ok(Self(Inner::Native(None)))
719+
Ok(Self::new())
707720
} else {
708-
Ok(Self(Inner::Native(Some(s.into()))))
721+
Ok(Self(Inner::Native(s.into())))
709722
}
710723
}
711724
// rustdoc-stripper-ignore-next
@@ -740,9 +753,9 @@ impl GString {
740753
String::from_utf8_unchecked(v)
741754
};
742755
if s.len() == 1 {
743-
Self(Inner::Native(None))
756+
Self::new()
744757
} else {
745-
Self(Inner::Native(Some(s.into())))
758+
Self(Inner::Native(s.into()))
746759
}
747760
}
748761
// rustdoc-stripper-ignore-next
@@ -758,14 +771,14 @@ impl GString {
758771
return Err(GStringNoTrailingNulError(bytes).into());
759772
};
760773
if nul_pos == 0 {
761-
Ok(Self(Inner::Native(None)))
774+
Ok(Self::new())
762775
} else {
763776
if let Err(e) = std::str::from_utf8(unsafe { bytes.get_unchecked(..nul_pos) }) {
764777
return Err(GStringUtf8Error(bytes, e).into());
765778
}
766779
bytes.truncate(nul_pos + 1);
767780
let s = unsafe { String::from_utf8_unchecked(bytes) };
768-
Ok(Self(Inner::Native(Some(s.into()))))
781+
Ok(Self(Inner::Native(s.into())))
769782
}
770783
}
771784
// rustdoc-stripper-ignore-next
@@ -789,11 +802,11 @@ impl GString {
789802
#[inline]
790803
pub fn from_string_unchecked(mut s: String) -> Self {
791804
if s.is_empty() {
792-
Self(Inner::Native(None))
805+
Self::new()
793806
} else {
794807
s.reserve_exact(1);
795808
s.push('\0');
796-
Self(Inner::Native(Some(s.into())))
809+
Self(Inner::Native(s.into()))
797810
}
798811
}
799812
// rustdoc-stripper-ignore-next
@@ -828,9 +841,9 @@ impl GString {
828841
pub fn as_str(&self) -> &str {
829842
unsafe {
830843
let (ptr, len) = match self.0 {
831-
Inner::Native(None) => (ptr::null(), 0),
832-
Inner::Native(Some(ref s)) => (s.as_ptr() as *const u8, s.len() - 1),
844+
Inner::Native(ref s) => (s.as_ptr() as *const u8, s.len() - 1),
833845
Inner::Foreign { ptr, len } => (ptr.as_ptr() as *const u8, len),
846+
Inner::Inline { len, ref data } => (data.as_ptr(), len as usize),
834847
};
835848
if len == 0 {
836849
""
@@ -846,12 +859,12 @@ impl GString {
846859
#[inline]
847860
pub fn as_gstr(&self) -> &GStr {
848861
let bytes = match self.0 {
849-
Inner::Native(None) => return <&GStr>::default(),
850-
Inner::Native(Some(ref s)) => s.as_bytes(),
862+
Inner::Native(ref s) => s.as_bytes(),
851863
Inner::Foreign { len, .. } if len == 0 => &[0],
852864
Inner::Foreign { ptr, len } => unsafe {
853865
slice::from_raw_parts(ptr.as_ptr() as *const _, len + 1)
854866
},
867+
Inner::Inline { len, ref data } => unsafe { data.get_unchecked(..len as usize + 1) },
855868
};
856869
unsafe { GStr::from_utf8_with_nul_unchecked(bytes) }
857870
}
@@ -861,9 +874,9 @@ impl GString {
861874
#[inline]
862875
pub fn as_ptr(&self) -> *const c_char {
863876
match self.0 {
864-
Inner::Native(None) => <&GStr>::default().as_ptr(),
865-
Inner::Native(Some(ref s)) => s.as_ptr() as *const _,
877+
Inner::Native(ref s) => s.as_ptr() as *const _,
866878
Inner::Foreign { ptr, .. } => ptr.as_ptr(),
879+
Inner::Inline { ref data, .. } => data.as_ptr() as *const _,
867880
}
868881
}
869882

@@ -873,34 +886,34 @@ impl GString {
873886
/// The returned buffer is not guaranteed to contain a trailing nul-byte.
874887
pub fn into_bytes(mut self) -> Vec<u8> {
875888
match &mut self.0 {
876-
Inner::Native(s) => match s.take() {
877-
None => Vec::new(),
878-
Some(s) => {
879-
let mut s = String::from(s);
880-
let _nul = s.pop();
881-
debug_assert_eq!(_nul, Some('\0'));
882-
s.into_bytes()
883-
}
884-
},
889+
Inner::Native(s) => {
890+
let mut s = String::from(mem::replace(s, "".into()));
891+
let _nul = s.pop();
892+
debug_assert_eq!(_nul, Some('\0'));
893+
s.into_bytes()
894+
}
885895
Inner::Foreign { ptr, len } => {
886896
let bytes = unsafe { slice::from_raw_parts(ptr.as_ptr() as *const u8, *len - 1) };
887897
bytes.to_owned()
888898
}
899+
Inner::Inline { len, data } => {
900+
unsafe { data.get_unchecked(..*len as usize) }.to_owned()
901+
}
889902
}
890903
}
891904

892905
// rustdoc-stripper-ignore-next
893906
/// Consumes the `GString` and returns the underlying byte buffer, with trailing nul-byte.
894907
pub fn into_bytes_with_nul(mut self) -> Vec<u8> {
895908
match &mut self.0 {
896-
Inner::Native(s) => match s.take() {
897-
None => vec![0u8],
898-
Some(s) => str::into_boxed_bytes(s).into(),
899-
},
909+
Inner::Native(s) => str::into_boxed_bytes(mem::replace(s, "".into())).into(),
900910
Inner::Foreign { ptr, len } => {
901911
let bytes = unsafe { slice::from_raw_parts(ptr.as_ptr() as *const u8, *len) };
902912
bytes.to_owned()
903913
}
914+
Inner::Inline { len, data } => {
915+
unsafe { data.get_unchecked(..*len as usize + 1) }.to_owned()
916+
}
904917
}
905918
}
906919
}
@@ -1032,12 +1045,14 @@ impl IntoGlibPtr<*mut c_char> for GString {
10321045
/// Transform into a nul-terminated raw C string pointer.
10331046
unsafe fn into_glib_ptr(self) -> *mut c_char {
10341047
match self.0 {
1035-
Inner::Native(None) => ffi::g_malloc0(1) as *mut _,
1036-
Inner::Native(Some(ref s)) => ffi::g_strndup(s.as_ptr() as *const _, s.len()),
1048+
Inner::Native(ref s) => ffi::g_strndup(s.as_ptr() as *const _, s.len()),
10371049
Inner::Foreign { ptr, .. } => {
10381050
let _s = mem::ManuallyDrop::new(self);
10391051
ptr.as_ptr()
10401052
}
1053+
Inner::Inline { len, ref data } => {
1054+
ffi::g_strndup(data.as_ptr() as *const _, len as usize)
1055+
}
10411056
}
10421057
}
10431058
}
@@ -1285,22 +1300,22 @@ impl From<GString> for String {
12851300
#[inline]
12861301
fn from(mut s: GString) -> Self {
12871302
match &mut s.0 {
1288-
Inner::Native(s) => match s.take() {
1289-
None => Self::default(),
1290-
Some(s) => {
1291-
// Moves the underlying string
1292-
let mut s = String::from(s);
1293-
let _nul = s.pop();
1294-
debug_assert_eq!(_nul, Some('\0'));
1295-
s
1296-
}
1297-
},
1303+
Inner::Native(s) => {
1304+
// Moves the underlying string
1305+
let mut s = String::from(mem::replace(s, "".into()));
1306+
let _nul = s.pop();
1307+
debug_assert_eq!(_nul, Some('\0'));
1308+
s
1309+
}
12981310
Inner::Foreign { len, .. } if *len == 0 => String::new(),
12991311
Inner::Foreign { ptr, len } => unsafe {
13001312
// Creates a copy
13011313
let slice = slice::from_raw_parts(ptr.as_ptr() as *const u8, *len);
13021314
std::str::from_utf8_unchecked(slice).into()
13031315
},
1316+
Inner::Inline { len, data } => unsafe {
1317+
std::str::from_utf8_unchecked(data.get_unchecked(..*len as usize)).to_owned()
1318+
},
13041319
}
13051320
}
13061321
}
@@ -1357,12 +1372,12 @@ impl From<String> for GString {
13571372
GStr::check_interior_nuls(&s).unwrap();
13581373
}
13591374
if s.is_empty() {
1360-
Self(Inner::Native(None))
1375+
Self::new()
13611376
} else {
13621377
s.reserve_exact(1);
13631378
s.push('\0');
13641379
// No check for valid UTF-8 here
1365-
Self(Inner::Native(Some(s.into())))
1380+
Self(Inner::Native(s.into()))
13661381
}
13671382
}
13681383
}
@@ -1398,8 +1413,14 @@ impl From<&str> for GString {
13981413
if cfg!(debug_assertions) {
13991414
GStr::check_interior_nuls(s).unwrap();
14001415
}
1401-
if s.is_empty() {
1402-
return Self::default();
1416+
if s.len() < INLINE_LEN {
1417+
let mut data = <[u8; INLINE_LEN]>::default();
1418+
let b = s.as_bytes();
1419+
unsafe { data.get_unchecked_mut(..b.len()) }.copy_from_slice(b);
1420+
return Self(Inner::Inline {
1421+
len: b.len() as u8,
1422+
data,
1423+
});
14031424
}
14041425
// Allocates with the GLib allocator
14051426
unsafe {
@@ -1418,7 +1439,7 @@ impl TryFrom<CString> for GString {
14181439
#[inline]
14191440
fn try_from(value: CString) -> Result<Self, Self::Error> {
14201441
if value.as_bytes().is_empty() {
1421-
Ok(Self(Inner::Native(None)))
1442+
Ok(Self::new())
14221443
} else {
14231444
// Moves the content of the CString
14241445
// Also check if it's valid UTF-8
@@ -1429,7 +1450,7 @@ impl TryFrom<CString> for GString {
14291450
err,
14301451
)
14311452
})?;
1432-
Ok(Self(Inner::Native(Some(s.into()))))
1453+
Ok(Self(Inner::Native(s.into())))
14331454
}
14341455
}
14351456
}
@@ -2094,4 +2115,15 @@ mod tests {
20942115
let s = gformat!("bla bla {} bla", 123);
20952116
assert_eq!(s, "bla bla 123 bla");
20962117
}
2118+
2119+
#[test]
2120+
fn layout() {
2121+
// ensure the inline variant is not wider than the other variants
2122+
enum NoInline {
2123+
_Native(Box<str>),
2124+
_Foreign(ptr::NonNull<c_char>, usize),
2125+
}
2126+
assert_eq!(mem::size_of::<GString>(), mem::size_of::<NoInline>());
2127+
assert_eq!(mem::size_of::<GString>(), mem::size_of::<String>());
2128+
}
20972129
}

0 commit comments

Comments
 (0)