@@ -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`].
@@ -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