Skip to content

Commit 671fa05

Browse files
committed
glib: allow GString to store small inline strings
1 parent 50e2d2b commit 671fa05

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

449449
#[inline]
450450
fn to_owned(&self) -> Self::Owned {
451-
if self.is_empty() {
452-
return GString::default();
453-
}
454-
// Always copy with the GLib allocator
455451
let b = self.as_bytes_with_nul();
452+
if self.len() < INLINE_LEN {
453+
let mut data = <[u8; INLINE_LEN]>::default();
454+
let b = self.as_bytes();
455+
unsafe { data.get_unchecked_mut(..b.len()) }.copy_from_slice(b);
456+
return GString(Inner::Inline {
457+
len: self.len() as u8,
458+
data,
459+
});
460+
}
456461
let inner = unsafe {
457462
let copy = ffi::g_strndup(b.as_ptr() as *const c_char, b.len());
458463
Inner::Foreign {
@@ -603,12 +608,17 @@ const INLINE_LEN: usize =
603608
/// The constructors beginning with `from_utf8` `and `from_string` can also be used to further
604609
/// control how interior nul-bytes are handled.
605610
pub struct GString(Inner);
611+
606612
enum Inner {
607-
Native(Option<Box<str>>),
613+
Native(Box<str>),
608614
Foreign {
609615
ptr: ptr::NonNull<c_char>,
610616
len: usize,
611617
},
618+
Inline {
619+
len: u8,
620+
data: [u8; INLINE_LEN],
621+
},
612622
}
613623

614624
unsafe impl Send for GString {}
@@ -621,7 +631,10 @@ impl GString {
621631
/// Does not allocate.
622632
#[inline]
623633
pub fn new() -> Self {
624-
Self(Inner::Native(None))
634+
Self(Inner::Inline {
635+
len: 0,
636+
data: Default::default(),
637+
})
625638
}
626639
// rustdoc-stripper-ignore-next
627640
/// Formats an [`Arguments`](std::fmt::Arguments) into a [`GString`].
@@ -685,11 +698,11 @@ impl GString {
685698
#[inline]
686699
pub unsafe fn from_utf8_unchecked(mut v: Vec<u8>) -> Self {
687700
if v.is_empty() {
688-
Self(Inner::Native(None))
701+
Self::new()
689702
} else {
690703
v.reserve_exact(1);
691704
v.push(0);
692-
Self(Inner::Native(Some(String::from_utf8_unchecked(v).into())))
705+
Self(Inner::Native(String::from_utf8_unchecked(v).into()))
693706
}
694707
}
695708
// rustdoc-stripper-ignore-next
@@ -705,9 +718,9 @@ impl GString {
705718
return Err(GStringNoTrailingNulError(s.into_bytes()).into());
706719
}
707720
if s.len() == 1 {
708-
Ok(Self(Inner::Native(None)))
721+
Ok(Self::new())
709722
} else {
710-
Ok(Self(Inner::Native(Some(s.into()))))
723+
Ok(Self(Inner::Native(s.into())))
711724
}
712725
}
713726
// rustdoc-stripper-ignore-next
@@ -742,9 +755,9 @@ impl GString {
742755
String::from_utf8_unchecked(v)
743756
};
744757
if s.len() == 1 {
745-
Self(Inner::Native(None))
758+
Self::new()
746759
} else {
747-
Self(Inner::Native(Some(s.into())))
760+
Self(Inner::Native(s.into()))
748761
}
749762
}
750763
// rustdoc-stripper-ignore-next
@@ -760,14 +773,14 @@ impl GString {
760773
return Err(GStringNoTrailingNulError(bytes).into());
761774
};
762775
if nul_pos == 0 {
763-
Ok(Self(Inner::Native(None)))
776+
Ok(Self::new())
764777
} else {
765778
if let Err(e) = std::str::from_utf8(unsafe { bytes.get_unchecked(..nul_pos) }) {
766779
return Err(GStringUtf8Error(bytes, e).into());
767780
}
768781
bytes.truncate(nul_pos + 1);
769782
let s = unsafe { String::from_utf8_unchecked(bytes) };
770-
Ok(Self(Inner::Native(Some(s.into()))))
783+
Ok(Self(Inner::Native(s.into())))
771784
}
772785
}
773786
// rustdoc-stripper-ignore-next
@@ -791,11 +804,11 @@ impl GString {
791804
#[inline]
792805
pub fn from_string_unchecked(mut s: String) -> Self {
793806
if s.is_empty() {
794-
Self(Inner::Native(None))
807+
Self::new()
795808
} else {
796809
s.reserve_exact(1);
797810
s.push('\0');
798-
Self(Inner::Native(Some(s.into())))
811+
Self(Inner::Native(s.into()))
799812
}
800813
}
801814
// rustdoc-stripper-ignore-next
@@ -830,9 +843,9 @@ impl GString {
830843
pub fn as_str(&self) -> &str {
831844
unsafe {
832845
let (ptr, len) = match self.0 {
833-
Inner::Native(None) => (ptr::null(), 0),
834-
Inner::Native(Some(ref s)) => (s.as_ptr() as *const u8, s.len() - 1),
846+
Inner::Native(ref s) => (s.as_ptr() as *const u8, s.len() - 1),
835847
Inner::Foreign { ptr, len } => (ptr.as_ptr() as *const u8, len),
848+
Inner::Inline { len, ref data } => (data.as_ptr(), len as usize),
836849
};
837850
if len == 0 {
838851
""
@@ -848,12 +861,12 @@ impl GString {
848861
#[inline]
849862
pub fn as_gstr(&self) -> &GStr {
850863
let bytes = match self.0 {
851-
Inner::Native(None) => return <&GStr>::default(),
852-
Inner::Native(Some(ref s)) => s.as_bytes(),
864+
Inner::Native(ref s) => s.as_bytes(),
853865
Inner::Foreign { len, .. } if len == 0 => &[0],
854866
Inner::Foreign { ptr, len } => unsafe {
855867
slice::from_raw_parts(ptr.as_ptr() as *const _, len + 1)
856868
},
869+
Inner::Inline { len, ref data } => unsafe { data.get_unchecked(..len as usize + 1) },
857870
};
858871
unsafe { GStr::from_utf8_with_nul_unchecked(bytes) }
859872
}
@@ -863,9 +876,9 @@ impl GString {
863876
#[inline]
864877
pub fn as_ptr(&self) -> *const c_char {
865878
match self.0 {
866-
Inner::Native(None) => <&GStr>::default().as_ptr(),
867-
Inner::Native(Some(ref s)) => s.as_ptr() as *const _,
879+
Inner::Native(ref s) => s.as_ptr() as *const _,
868880
Inner::Foreign { ptr, .. } => ptr.as_ptr(),
881+
Inner::Inline { ref data, .. } => data.as_ptr() as *const _,
869882
}
870883
}
871884

@@ -875,34 +888,34 @@ impl GString {
875888
/// The returned buffer is not guaranteed to contain a trailing nul-byte.
876889
pub fn into_bytes(mut self) -> Vec<u8> {
877890
match &mut self.0 {
878-
Inner::Native(s) => match s.take() {
879-
None => Vec::new(),
880-
Some(s) => {
881-
let mut s = String::from(s);
882-
let _nul = s.pop();
883-
debug_assert_eq!(_nul, Some('\0'));
884-
s.into_bytes()
885-
}
886-
},
891+
Inner::Native(s) => {
892+
let mut s = String::from(mem::replace(s, "".into()));
893+
let _nul = s.pop();
894+
debug_assert_eq!(_nul, Some('\0'));
895+
s.into_bytes()
896+
}
887897
Inner::Foreign { ptr, len } => {
888898
let bytes = unsafe { slice::from_raw_parts(ptr.as_ptr() as *const u8, *len - 1) };
889899
bytes.to_owned()
890900
}
901+
Inner::Inline { len, data } => {
902+
unsafe { data.get_unchecked(..*len as usize) }.to_owned()
903+
}
891904
}
892905
}
893906

894907
// rustdoc-stripper-ignore-next
895908
/// Consumes the `GString` and returns the underlying byte buffer, with trailing nul-byte.
896909
pub fn into_bytes_with_nul(mut self) -> Vec<u8> {
897910
match &mut self.0 {
898-
Inner::Native(s) => match s.take() {
899-
None => vec![0u8],
900-
Some(s) => str::into_boxed_bytes(s).into(),
901-
},
911+
Inner::Native(s) => str::into_boxed_bytes(mem::replace(s, "".into())).into(),
902912
Inner::Foreign { ptr, len } => {
903913
let bytes = unsafe { slice::from_raw_parts(ptr.as_ptr() as *const u8, *len) };
904914
bytes.to_owned()
905915
}
916+
Inner::Inline { len, data } => {
917+
unsafe { data.get_unchecked(..*len as usize + 1) }.to_owned()
918+
}
906919
}
907920
}
908921
}
@@ -1034,12 +1047,14 @@ impl IntoGlibPtr<*mut c_char> for GString {
10341047
/// Transform into a nul-terminated raw C string pointer.
10351048
unsafe fn into_glib_ptr(self) -> *mut c_char {
10361049
match self.0 {
1037-
Inner::Native(None) => ffi::g_malloc0(1) as *mut _,
1038-
Inner::Native(Some(ref s)) => ffi::g_strndup(s.as_ptr() as *const _, s.len()),
1050+
Inner::Native(ref s) => ffi::g_strndup(s.as_ptr() as *const _, s.len()),
10391051
Inner::Foreign { ptr, .. } => {
10401052
let _s = mem::ManuallyDrop::new(self);
10411053
ptr.as_ptr()
10421054
}
1055+
Inner::Inline { len, ref data } => {
1056+
ffi::g_strndup(data.as_ptr() as *const _, len as usize)
1057+
}
10431058
}
10441059
}
10451060
}
@@ -1287,22 +1302,22 @@ impl From<GString> for String {
12871302
#[inline]
12881303
fn from(mut s: GString) -> Self {
12891304
match &mut s.0 {
1290-
Inner::Native(s) => match s.take() {
1291-
None => Self::default(),
1292-
Some(s) => {
1293-
// Moves the underlying string
1294-
let mut s = String::from(s);
1295-
let _nul = s.pop();
1296-
debug_assert_eq!(_nul, Some('\0'));
1297-
s
1298-
}
1299-
},
1305+
Inner::Native(s) => {
1306+
// Moves the underlying string
1307+
let mut s = String::from(mem::replace(s, "".into()));
1308+
let _nul = s.pop();
1309+
debug_assert_eq!(_nul, Some('\0'));
1310+
s
1311+
}
13001312
Inner::Foreign { len, .. } if *len == 0 => String::new(),
13011313
Inner::Foreign { ptr, len } => unsafe {
13021314
// Creates a copy
13031315
let slice = slice::from_raw_parts(ptr.as_ptr() as *const u8, *len);
13041316
std::str::from_utf8_unchecked(slice).into()
13051317
},
1318+
Inner::Inline { len, data } => unsafe {
1319+
std::str::from_utf8_unchecked(data.get_unchecked(..*len as usize)).to_owned()
1320+
},
13061321
}
13071322
}
13081323
}
@@ -1359,12 +1374,12 @@ impl From<String> for GString {
13591374
GStr::check_interior_nuls(&s).unwrap();
13601375
}
13611376
if s.is_empty() {
1362-
Self(Inner::Native(None))
1377+
Self::new()
13631378
} else {
13641379
s.reserve_exact(1);
13651380
s.push('\0');
13661381
// No check for valid UTF-8 here
1367-
Self(Inner::Native(Some(s.into())))
1382+
Self(Inner::Native(s.into()))
13681383
}
13691384
}
13701385
}
@@ -1400,8 +1415,14 @@ impl From<&str> for GString {
14001415
if cfg!(debug_assertions) {
14011416
GStr::check_interior_nuls(s).unwrap();
14021417
}
1403-
if s.is_empty() {
1404-
return Self::default();
1418+
if s.len() < INLINE_LEN {
1419+
let mut data = <[u8; INLINE_LEN]>::default();
1420+
let b = s.as_bytes();
1421+
unsafe { data.get_unchecked_mut(..b.len()) }.copy_from_slice(b);
1422+
return Self(Inner::Inline {
1423+
len: b.len() as u8,
1424+
data,
1425+
});
14051426
}
14061427
// Allocates with the GLib allocator
14071428
unsafe {
@@ -1420,7 +1441,7 @@ impl TryFrom<CString> for GString {
14201441
#[inline]
14211442
fn try_from(value: CString) -> Result<Self, Self::Error> {
14221443
if value.as_bytes().is_empty() {
1423-
Ok(Self(Inner::Native(None)))
1444+
Ok(Self::new())
14241445
} else {
14251446
// Moves the content of the CString
14261447
// Also check if it's valid UTF-8
@@ -1431,7 +1452,7 @@ impl TryFrom<CString> for GString {
14311452
err,
14321453
)
14331454
})?;
1434-
Ok(Self(Inner::Native(Some(s.into()))))
1455+
Ok(Self(Inner::Native(s.into())))
14351456
}
14361457
}
14371458
}
@@ -2096,4 +2117,15 @@ mod tests {
20962117
let s = gformat!("bla bla {} bla", 123);
20972118
assert_eq!(s, "bla bla 123 bla");
20982119
}
2120+
2121+
#[test]
2122+
fn layout() {
2123+
// ensure the inline variant is not wider than the other variants
2124+
enum NoInline {
2125+
_Native(Box<str>),
2126+
_Foreign(ptr::NonNull<c_char>, usize),
2127+
}
2128+
assert_eq!(mem::size_of::<GString>(), mem::size_of::<NoInline>());
2129+
assert_eq!(mem::size_of::<GString>(), mem::size_of::<String>());
2130+
}
20992131
}

0 commit comments

Comments
 (0)