@@ -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.
605610pub struct GString ( Inner ) ;
611+
606612enum 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
614624unsafe 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