@@ -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.
613618pub struct GString ( Inner ) ;
619+
614620enum 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
622632unsafe 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`].
@@ -693,11 +706,11 @@ impl GString {
693706 #[ inline]
694707 pub unsafe fn from_utf8_unchecked ( mut v : Vec < u8 > ) -> Self {
695708 if v. is_empty ( ) {
696- Self ( Inner :: Native ( None ) )
709+ Self :: new ( )
697710 } else {
698711 v. reserve_exact ( 1 ) ;
699712 v. push ( 0 ) ;
700- Self ( Inner :: Native ( Some ( String :: from_utf8_unchecked ( v) . into ( ) ) ) )
713+ Self ( Inner :: Native ( String :: from_utf8_unchecked ( v) . into ( ) ) )
701714 }
702715 }
703716 // rustdoc-stripper-ignore-next
@@ -713,9 +726,9 @@ impl GString {
713726 return Err ( GStringNoTrailingNulError ( s. into_bytes ( ) ) . into ( ) ) ;
714727 }
715728 if s. len ( ) == 1 {
716- Ok ( Self ( Inner :: Native ( None ) ) )
729+ Ok ( Self :: new ( ) )
717730 } else {
718- Ok ( Self ( Inner :: Native ( Some ( s. into ( ) ) ) ) )
731+ Ok ( Self ( Inner :: Native ( s. into ( ) ) ) )
719732 }
720733 }
721734 // rustdoc-stripper-ignore-next
@@ -750,9 +763,9 @@ impl GString {
750763 String :: from_utf8_unchecked ( v)
751764 } ;
752765 if s. len ( ) == 1 {
753- Self ( Inner :: Native ( None ) )
766+ Self :: new ( )
754767 } else {
755- Self ( Inner :: Native ( Some ( s. into ( ) ) ) )
768+ Self ( Inner :: Native ( s. into ( ) ) )
756769 }
757770 }
758771 // rustdoc-stripper-ignore-next
@@ -768,14 +781,14 @@ impl GString {
768781 return Err ( GStringNoTrailingNulError ( bytes) . into ( ) ) ;
769782 } ;
770783 if nul_pos == 0 {
771- Ok ( Self ( Inner :: Native ( None ) ) )
784+ Ok ( Self :: new ( ) )
772785 } else {
773786 if let Err ( e) = std:: str:: from_utf8 ( unsafe { bytes. get_unchecked ( ..nul_pos) } ) {
774787 return Err ( GStringUtf8Error ( bytes, e) . into ( ) ) ;
775788 }
776789 bytes. truncate ( nul_pos + 1 ) ;
777790 let s = unsafe { String :: from_utf8_unchecked ( bytes) } ;
778- Ok ( Self ( Inner :: Native ( Some ( s. into ( ) ) ) ) )
791+ Ok ( Self ( Inner :: Native ( s. into ( ) ) ) )
779792 }
780793 }
781794 // rustdoc-stripper-ignore-next
@@ -799,11 +812,11 @@ impl GString {
799812 #[ inline]
800813 pub fn from_string_unchecked ( mut s : String ) -> Self {
801814 if s. is_empty ( ) {
802- Self ( Inner :: Native ( None ) )
815+ Self :: new ( )
803816 } else {
804817 s. reserve_exact ( 1 ) ;
805818 s. push ( '\0' ) ;
806- Self ( Inner :: Native ( Some ( s. into ( ) ) ) )
819+ Self ( Inner :: Native ( s. into ( ) ) )
807820 }
808821 }
809822 // rustdoc-stripper-ignore-next
@@ -838,9 +851,9 @@ impl GString {
838851 pub fn as_str ( & self ) -> & str {
839852 unsafe {
840853 let ( ptr, len) = match self . 0 {
841- Inner :: Native ( None ) => ( ptr:: null ( ) , 0 ) ,
842- Inner :: Native ( Some ( ref s) ) => ( s. as_ptr ( ) as * const u8 , s. len ( ) - 1 ) ,
854+ Inner :: Native ( ref s) => ( s. as_ptr ( ) as * const u8 , s. len ( ) - 1 ) ,
843855 Inner :: Foreign { ptr, len } => ( ptr. as_ptr ( ) as * const u8 , len) ,
856+ Inner :: Inline { len, ref data } => ( data. as_ptr ( ) , len as usize ) ,
844857 } ;
845858 if len == 0 {
846859 ""
@@ -856,12 +869,12 @@ impl GString {
856869 #[ inline]
857870 pub fn as_gstr ( & self ) -> & GStr {
858871 let bytes = match self . 0 {
859- Inner :: Native ( None ) => return <& GStr >:: default ( ) ,
860- Inner :: Native ( Some ( ref s) ) => s. as_bytes ( ) ,
872+ Inner :: Native ( ref s) => s. as_bytes ( ) ,
861873 Inner :: Foreign { len, .. } if len == 0 => & [ 0 ] ,
862874 Inner :: Foreign { ptr, len } => unsafe {
863875 slice:: from_raw_parts ( ptr. as_ptr ( ) as * const _ , len + 1 )
864876 } ,
877+ Inner :: Inline { len, ref data } => unsafe { data. get_unchecked ( ..len as usize + 1 ) } ,
865878 } ;
866879 unsafe { GStr :: from_utf8_with_nul_unchecked ( bytes) }
867880 }
@@ -871,9 +884,9 @@ impl GString {
871884 #[ inline]
872885 pub fn as_ptr ( & self ) -> * const c_char {
873886 match self . 0 {
874- Inner :: Native ( None ) => <& GStr >:: default ( ) . as_ptr ( ) ,
875- Inner :: Native ( Some ( ref s) ) => s. as_ptr ( ) as * const _ ,
887+ Inner :: Native ( ref s) => s. as_ptr ( ) as * const _ ,
876888 Inner :: Foreign { ptr, .. } => ptr. as_ptr ( ) ,
889+ Inner :: Inline { ref data, .. } => data. as_ptr ( ) as * const _ ,
877890 }
878891 }
879892
@@ -883,34 +896,34 @@ impl GString {
883896 /// The returned buffer is not guaranteed to contain a trailing nul-byte.
884897 pub fn into_bytes ( mut self ) -> Vec < u8 > {
885898 match & mut self . 0 {
886- Inner :: Native ( s) => match s. take ( ) {
887- None => Vec :: new ( ) ,
888- Some ( s) => {
889- let mut s = String :: from ( s) ;
890- let _nul = s. pop ( ) ;
891- debug_assert_eq ! ( _nul, Some ( '\0' ) ) ;
892- s. into_bytes ( )
893- }
894- } ,
899+ Inner :: Native ( s) => {
900+ let mut s = String :: from ( mem:: replace ( s, "" . into ( ) ) ) ;
901+ let _nul = s. pop ( ) ;
902+ debug_assert_eq ! ( _nul, Some ( '\0' ) ) ;
903+ s. into_bytes ( )
904+ }
895905 Inner :: Foreign { ptr, len } => {
896906 let bytes = unsafe { slice:: from_raw_parts ( ptr. as_ptr ( ) as * const u8 , * len - 1 ) } ;
897907 bytes. to_owned ( )
898908 }
909+ Inner :: Inline { len, data } => {
910+ unsafe { data. get_unchecked ( ..* len as usize ) } . to_owned ( )
911+ }
899912 }
900913 }
901914
902915 // rustdoc-stripper-ignore-next
903916 /// Consumes the `GString` and returns the underlying byte buffer, with trailing nul-byte.
904917 pub fn into_bytes_with_nul ( mut self ) -> Vec < u8 > {
905918 match & mut self . 0 {
906- Inner :: Native ( s) => match s. take ( ) {
907- None => vec ! [ 0u8 ] ,
908- Some ( s) => str:: into_boxed_bytes ( s) . into ( ) ,
909- } ,
919+ Inner :: Native ( s) => str:: into_boxed_bytes ( mem:: replace ( s, "" . into ( ) ) ) . into ( ) ,
910920 Inner :: Foreign { ptr, len } => {
911921 let bytes = unsafe { slice:: from_raw_parts ( ptr. as_ptr ( ) as * const u8 , * len) } ;
912922 bytes. to_owned ( )
913923 }
924+ Inner :: Inline { len, data } => {
925+ unsafe { data. get_unchecked ( ..* len as usize + 1 ) } . to_owned ( )
926+ }
914927 }
915928 }
916929}
@@ -1042,12 +1055,14 @@ impl IntoGlibPtr<*mut c_char> for GString {
10421055 /// Transform into a nul-terminated raw C string pointer.
10431056 unsafe fn into_glib_ptr ( self ) -> * mut c_char {
10441057 match self . 0 {
1045- Inner :: Native ( None ) => ffi:: g_malloc0 ( 1 ) as * mut _ ,
1046- Inner :: Native ( Some ( ref s) ) => ffi:: g_strndup ( s. as_ptr ( ) as * const _ , s. len ( ) ) ,
1058+ Inner :: Native ( ref s) => ffi:: g_strndup ( s. as_ptr ( ) as * const _ , s. len ( ) ) ,
10471059 Inner :: Foreign { ptr, .. } => {
10481060 let _s = mem:: ManuallyDrop :: new ( self ) ;
10491061 ptr. as_ptr ( )
10501062 }
1063+ Inner :: Inline { len, ref data } => {
1064+ ffi:: g_strndup ( data. as_ptr ( ) as * const _ , len as usize )
1065+ }
10511066 }
10521067 }
10531068}
@@ -1295,22 +1310,22 @@ impl From<GString> for String {
12951310 #[ inline]
12961311 fn from ( mut s : GString ) -> Self {
12971312 match & mut s. 0 {
1298- Inner :: Native ( s) => match s. take ( ) {
1299- None => Self :: default ( ) ,
1300- Some ( s) => {
1301- // Moves the underlying string
1302- let mut s = String :: from ( s) ;
1303- let _nul = s. pop ( ) ;
1304- debug_assert_eq ! ( _nul, Some ( '\0' ) ) ;
1305- s
1306- }
1307- } ,
1313+ Inner :: Native ( s) => {
1314+ // Moves the underlying string
1315+ let mut s = String :: from ( mem:: replace ( s, "" . into ( ) ) ) ;
1316+ let _nul = s. pop ( ) ;
1317+ debug_assert_eq ! ( _nul, Some ( '\0' ) ) ;
1318+ s
1319+ }
13081320 Inner :: Foreign { len, .. } if * len == 0 => String :: new ( ) ,
13091321 Inner :: Foreign { ptr, len } => unsafe {
13101322 // Creates a copy
13111323 let slice = slice:: from_raw_parts ( ptr. as_ptr ( ) as * const u8 , * len) ;
13121324 std:: str:: from_utf8_unchecked ( slice) . into ( )
13131325 } ,
1326+ Inner :: Inline { len, data } => unsafe {
1327+ std:: str:: from_utf8_unchecked ( data. get_unchecked ( ..* len as usize ) ) . to_owned ( )
1328+ } ,
13141329 }
13151330 }
13161331}
@@ -1367,12 +1382,12 @@ impl From<String> for GString {
13671382 GStr :: check_interior_nuls ( & s) . unwrap ( ) ;
13681383 }
13691384 if s. is_empty ( ) {
1370- Self ( Inner :: Native ( None ) )
1385+ Self :: new ( )
13711386 } else {
13721387 s. reserve_exact ( 1 ) ;
13731388 s. push ( '\0' ) ;
13741389 // No check for valid UTF-8 here
1375- Self ( Inner :: Native ( Some ( s. into ( ) ) ) )
1390+ Self ( Inner :: Native ( s. into ( ) ) )
13761391 }
13771392 }
13781393}
@@ -1408,8 +1423,14 @@ impl From<&str> for GString {
14081423 if cfg ! ( debug_assertions) {
14091424 GStr :: check_interior_nuls ( s) . unwrap ( ) ;
14101425 }
1411- if s. is_empty ( ) {
1412- return Self :: default ( ) ;
1426+ if s. len ( ) < INLINE_LEN {
1427+ let mut data = <[ u8 ; INLINE_LEN ] >:: default ( ) ;
1428+ let b = s. as_bytes ( ) ;
1429+ unsafe { data. get_unchecked_mut ( ..b. len ( ) ) } . copy_from_slice ( b) ;
1430+ return Self ( Inner :: Inline {
1431+ len : b. len ( ) as u8 ,
1432+ data,
1433+ } ) ;
14131434 }
14141435 // Allocates with the GLib allocator
14151436 unsafe {
@@ -1428,7 +1449,7 @@ impl TryFrom<CString> for GString {
14281449 #[ inline]
14291450 fn try_from ( value : CString ) -> Result < Self , Self :: Error > {
14301451 if value. as_bytes ( ) . is_empty ( ) {
1431- Ok ( Self ( Inner :: Native ( None ) ) )
1452+ Ok ( Self :: new ( ) )
14321453 } else {
14331454 // Moves the content of the CString
14341455 // Also check if it's valid UTF-8
@@ -1439,7 +1460,7 @@ impl TryFrom<CString> for GString {
14391460 err,
14401461 )
14411462 } ) ?;
1442- Ok ( Self ( Inner :: Native ( Some ( s. into ( ) ) ) ) )
1463+ Ok ( Self ( Inner :: Native ( s. into ( ) ) ) )
14431464 }
14441465 }
14451466}
@@ -2104,4 +2125,15 @@ mod tests {
21042125 let s = gformat ! ( "bla bla {} bla" , 123 ) ;
21052126 assert_eq ! ( s, "bla bla 123 bla" ) ;
21062127 }
2128+
2129+ #[ test]
2130+ fn layout ( ) {
2131+ // ensure the inline variant is not wider than the other variants
2132+ enum NoInline {
2133+ _Native( Box < str > ) ,
2134+ _Foreign( ptr:: NonNull < c_char > , usize ) ,
2135+ }
2136+ assert_eq ! ( mem:: size_of:: <GString >( ) , mem:: size_of:: <NoInline >( ) ) ;
2137+ assert_eq ! ( mem:: size_of:: <GString >( ) , mem:: size_of:: <String >( ) ) ;
2138+ }
21072139}
0 commit comments