@@ -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.
611616pub struct GString ( Inner ) ;
617+
612618enum 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
620630unsafe 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