@@ -438,11 +438,16 @@ impl ToOwned for GStr {
438438
439439 #[ inline]
440440 fn to_owned ( & self ) -> Self :: Owned {
441- if self . is_empty ( ) {
442- return GString :: default ( ) ;
443- }
444- // Always copy with the GLib allocator
445441 let b = self . as_bytes_with_nul ( ) ;
442+ if self . len ( ) < INLINE_LEN {
443+ let mut data = <[ u8 ; INLINE_LEN ] >:: default ( ) ;
444+ let b = self . as_bytes ( ) ;
445+ unsafe { data. get_unchecked_mut ( ..b. len ( ) ) } . copy_from_slice ( b) ;
446+ return GString ( Inner :: Inline {
447+ len : self . len ( ) as u8 ,
448+ data,
449+ } ) ;
450+ }
446451 let inner = unsafe {
447452 let copy = ffi:: g_strndup ( b. as_ptr ( ) as * const c_char , b. len ( ) ) ;
448453 Inner :: Foreign {
@@ -594,12 +599,17 @@ const INLINE_LEN: usize =
594599/// control how interior nul-bytes are handled.
595600#[ repr( transparent) ]
596601pub struct GString ( Inner ) ;
602+
597603enum Inner {
598- Native ( Option < Box < str > > ) ,
604+ Native ( Box < str > ) ,
599605 Foreign {
600606 ptr : ptr:: NonNull < c_char > ,
601607 len : usize ,
602608 } ,
609+ Inline {
610+ len : u8 ,
611+ data : [ u8 ; INLINE_LEN ] ,
612+ } ,
603613}
604614
605615unsafe impl Send for GString { }
@@ -612,7 +622,10 @@ impl GString {
612622 /// Does not allocate.
613623 #[ inline]
614624 pub fn new ( ) -> Self {
615- Self ( Inner :: Native ( None ) )
625+ Self ( Inner :: Inline {
626+ len : 0 ,
627+ data : Default :: default ( ) ,
628+ } )
616629 }
617630 // rustdoc-stripper-ignore-next
618631 /// Formats an [`Arguments`](std::fmt::Arguments) into a [`GString`].
@@ -676,11 +689,11 @@ impl GString {
676689 #[ inline]
677690 pub unsafe fn from_utf8_unchecked ( mut v : Vec < u8 > ) -> Self {
678691 if v. is_empty ( ) {
679- Self ( Inner :: Native ( None ) )
692+ Self :: new ( )
680693 } else {
681694 v. reserve_exact ( 1 ) ;
682695 v. push ( 0 ) ;
683- Self ( Inner :: Native ( Some ( String :: from_utf8_unchecked ( v) . into ( ) ) ) )
696+ Self ( Inner :: Native ( String :: from_utf8_unchecked ( v) . into ( ) ) )
684697 }
685698 }
686699 // rustdoc-stripper-ignore-next
@@ -696,9 +709,9 @@ impl GString {
696709 return Err ( GStringNoTrailingNulError ( s. into_bytes ( ) ) . into ( ) ) ;
697710 }
698711 if s. len ( ) == 1 {
699- Ok ( Self ( Inner :: Native ( None ) ) )
712+ Ok ( Self :: new ( ) )
700713 } else {
701- Ok ( Self ( Inner :: Native ( Some ( s. into ( ) ) ) ) )
714+ Ok ( Self ( Inner :: Native ( s. into ( ) ) ) )
702715 }
703716 }
704717 // rustdoc-stripper-ignore-next
@@ -733,9 +746,9 @@ impl GString {
733746 String :: from_utf8_unchecked ( v)
734747 } ;
735748 if s. len ( ) == 1 {
736- Self ( Inner :: Native ( None ) )
749+ Self :: new ( )
737750 } else {
738- Self ( Inner :: Native ( Some ( s. into ( ) ) ) )
751+ Self ( Inner :: Native ( s. into ( ) ) )
739752 }
740753 }
741754 // rustdoc-stripper-ignore-next
@@ -751,14 +764,14 @@ impl GString {
751764 return Err ( GStringNoTrailingNulError ( bytes) . into ( ) ) ;
752765 } ;
753766 if nul_pos == 0 {
754- Ok ( Self ( Inner :: Native ( None ) ) )
767+ Ok ( Self :: new ( ) )
755768 } else {
756769 if let Err ( e) = std:: str:: from_utf8 ( unsafe { bytes. get_unchecked ( ..nul_pos) } ) {
757770 return Err ( GStringUtf8Error ( bytes, e) . into ( ) ) ;
758771 }
759772 bytes. truncate ( nul_pos + 1 ) ;
760773 let s = unsafe { String :: from_utf8_unchecked ( bytes) } ;
761- Ok ( Self ( Inner :: Native ( Some ( s. into ( ) ) ) ) )
774+ Ok ( Self ( Inner :: Native ( s. into ( ) ) ) )
762775 }
763776 }
764777 // rustdoc-stripper-ignore-next
@@ -782,11 +795,11 @@ impl GString {
782795 #[ inline]
783796 pub fn from_string_unchecked ( mut s : String ) -> Self {
784797 if s. is_empty ( ) {
785- Self ( Inner :: Native ( None ) )
798+ Self :: new ( )
786799 } else {
787800 s. reserve_exact ( 1 ) ;
788801 s. push ( '\0' ) ;
789- Self ( Inner :: Native ( Some ( s. into ( ) ) ) )
802+ Self ( Inner :: Native ( s. into ( ) ) )
790803 }
791804 }
792805 // rustdoc-stripper-ignore-next
@@ -795,9 +808,9 @@ impl GString {
795808 pub fn as_str ( & self ) -> & str {
796809 unsafe {
797810 let ( ptr, len) = match self . 0 {
798- Inner :: Native ( None ) => ( ptr:: null ( ) , 0 ) ,
799- Inner :: Native ( Some ( ref s) ) => ( s. as_ptr ( ) as * const u8 , s. len ( ) - 1 ) ,
811+ Inner :: Native ( ref s) => ( s. as_ptr ( ) as * const u8 , s. len ( ) - 1 ) ,
800812 Inner :: Foreign { ptr, len } => ( ptr. as_ptr ( ) as * const u8 , len) ,
813+ Inner :: Inline { len, ref data } => ( data. as_ptr ( ) , len as usize ) ,
801814 } ;
802815 if len == 0 {
803816 ""
@@ -813,12 +826,12 @@ impl GString {
813826 #[ inline]
814827 pub fn as_gstr ( & self ) -> & GStr {
815828 let bytes = match self . 0 {
816- Inner :: Native ( None ) => return <& GStr >:: default ( ) ,
817- Inner :: Native ( Some ( ref s) ) => s. as_bytes ( ) ,
829+ Inner :: Native ( ref s) => s. as_bytes ( ) ,
818830 Inner :: Foreign { len, .. } if len == 0 => & [ 0 ] ,
819831 Inner :: Foreign { ptr, len } => unsafe {
820832 slice:: from_raw_parts ( ptr. as_ptr ( ) as * const _ , len + 1 )
821833 } ,
834+ Inner :: Inline { len, ref data } => unsafe { data. get_unchecked ( ..len as usize + 1 ) } ,
822835 } ;
823836 unsafe { GStr :: from_utf8_with_nul_unchecked ( bytes) }
824837 }
@@ -828,9 +841,9 @@ impl GString {
828841 #[ inline]
829842 pub fn as_ptr ( & self ) -> * const c_char {
830843 match self . 0 {
831- Inner :: Native ( None ) => <& GStr >:: default ( ) . as_ptr ( ) ,
832- Inner :: Native ( Some ( ref s) ) => s. as_ptr ( ) as * const _ ,
844+ Inner :: Native ( ref s) => s. as_ptr ( ) as * const _ ,
833845 Inner :: Foreign { ptr, .. } => ptr. as_ptr ( ) ,
846+ Inner :: Inline { ref data, .. } => data. as_ptr ( ) as * const _ ,
834847 }
835848 }
836849
@@ -840,34 +853,34 @@ impl GString {
840853 /// The returned buffer is not guaranteed to contain a trailing nul-byte.
841854 pub fn into_bytes ( mut self ) -> Vec < u8 > {
842855 match & mut self . 0 {
843- Inner :: Native ( s) => match s. take ( ) {
844- None => Vec :: new ( ) ,
845- Some ( s) => {
846- let mut s = String :: from ( s) ;
847- let _nul = s. pop ( ) ;
848- debug_assert_eq ! ( _nul, Some ( '\0' ) ) ;
849- s. into_bytes ( )
850- }
851- } ,
856+ Inner :: Native ( s) => {
857+ let mut s = String :: from ( mem:: replace ( s, "" . into ( ) ) ) ;
858+ let _nul = s. pop ( ) ;
859+ debug_assert_eq ! ( _nul, Some ( '\0' ) ) ;
860+ s. into_bytes ( )
861+ }
852862 Inner :: Foreign { ptr, len } => {
853863 let bytes = unsafe { slice:: from_raw_parts ( ptr. as_ptr ( ) as * const u8 , * len - 1 ) } ;
854864 bytes. to_owned ( )
855865 }
866+ Inner :: Inline { len, data } => {
867+ unsafe { data. get_unchecked ( ..* len as usize ) } . to_owned ( )
868+ }
856869 }
857870 }
858871
859872 // rustdoc-stripper-ignore-next
860873 /// Consumes the `GString` and returns the underlying byte buffer, with trailing nul-byte.
861874 pub fn into_bytes_with_nul ( mut self ) -> Vec < u8 > {
862875 match & mut self . 0 {
863- Inner :: Native ( s) => match s. take ( ) {
864- None => vec ! [ 0u8 ] ,
865- Some ( s) => str:: into_boxed_bytes ( s) . into ( ) ,
866- } ,
876+ Inner :: Native ( s) => str:: into_boxed_bytes ( mem:: replace ( s, "" . into ( ) ) ) . into ( ) ,
867877 Inner :: Foreign { ptr, len } => {
868878 let bytes = unsafe { slice:: from_raw_parts ( ptr. as_ptr ( ) as * const u8 , * len) } ;
869879 bytes. to_owned ( )
870880 }
881+ Inner :: Inline { len, data } => {
882+ unsafe { data. get_unchecked ( ..* len as usize + 1 ) } . to_owned ( )
883+ }
871884 }
872885 }
873886}
@@ -999,12 +1012,14 @@ impl IntoGlibPtr<*mut c_char> for GString {
9991012 /// Transform into a nul-terminated raw C string pointer.
10001013 unsafe fn into_glib_ptr ( self ) -> * mut c_char {
10011014 match self . 0 {
1002- Inner :: Native ( None ) => ffi:: g_malloc0 ( 1 ) as * mut _ ,
1003- Inner :: Native ( Some ( ref s) ) => ffi:: g_strndup ( s. as_ptr ( ) as * const _ , s. len ( ) ) ,
1015+ Inner :: Native ( ref s) => ffi:: g_strndup ( s. as_ptr ( ) as * const _ , s. len ( ) ) ,
10041016 Inner :: Foreign { ptr, .. } => {
10051017 let _s = mem:: ManuallyDrop :: new ( self ) ;
10061018 ptr. as_ptr ( )
10071019 }
1020+ Inner :: Inline { len, ref data } => {
1021+ ffi:: g_strndup ( data. as_ptr ( ) as * const _ , len as usize )
1022+ }
10081023 }
10091024 }
10101025}
@@ -1252,22 +1267,22 @@ impl From<GString> for String {
12521267 #[ inline]
12531268 fn from ( mut s : GString ) -> Self {
12541269 match & mut s. 0 {
1255- Inner :: Native ( s) => match s. take ( ) {
1256- None => Self :: default ( ) ,
1257- Some ( s) => {
1258- // Moves the underlying string
1259- let mut s = String :: from ( s) ;
1260- let _nul = s. pop ( ) ;
1261- debug_assert_eq ! ( _nul, Some ( '\0' ) ) ;
1262- s
1263- }
1264- } ,
1270+ Inner :: Native ( s) => {
1271+ // Moves the underlying string
1272+ let mut s = String :: from ( mem:: replace ( s, "" . into ( ) ) ) ;
1273+ let _nul = s. pop ( ) ;
1274+ debug_assert_eq ! ( _nul, Some ( '\0' ) ) ;
1275+ s
1276+ }
12651277 Inner :: Foreign { len, .. } if * len == 0 => String :: new ( ) ,
12661278 Inner :: Foreign { ptr, len } => unsafe {
12671279 // Creates a copy
12681280 let slice = slice:: from_raw_parts ( ptr. as_ptr ( ) as * const u8 , * len) ;
12691281 std:: str:: from_utf8_unchecked ( slice) . into ( )
12701282 } ,
1283+ Inner :: Inline { len, data } => unsafe {
1284+ std:: str:: from_utf8_unchecked ( data. get_unchecked ( ..* len as usize ) ) . to_owned ( )
1285+ } ,
12711286 }
12721287 }
12731288}
@@ -1324,12 +1339,12 @@ impl From<String> for GString {
13241339 GStr :: check_interior_nuls ( & s) . unwrap ( ) ;
13251340 }
13261341 if s. is_empty ( ) {
1327- Self ( Inner :: Native ( None ) )
1342+ Self :: new ( )
13281343 } else {
13291344 s. reserve_exact ( 1 ) ;
13301345 s. push ( '\0' ) ;
13311346 // No check for valid UTF-8 here
1332- Self ( Inner :: Native ( Some ( s. into ( ) ) ) )
1347+ Self ( Inner :: Native ( s. into ( ) ) )
13331348 }
13341349 }
13351350}
@@ -1365,8 +1380,14 @@ impl From<&str> for GString {
13651380 if cfg ! ( debug_assertions) {
13661381 GStr :: check_interior_nuls ( s) . unwrap ( ) ;
13671382 }
1368- if s. is_empty ( ) {
1369- return Self :: default ( ) ;
1383+ if s. len ( ) < INLINE_LEN {
1384+ let mut data = <[ u8 ; INLINE_LEN ] >:: default ( ) ;
1385+ let b = s. as_bytes ( ) ;
1386+ unsafe { data. get_unchecked_mut ( ..b. len ( ) ) } . copy_from_slice ( b) ;
1387+ return Self ( Inner :: Inline {
1388+ len : b. len ( ) as u8 ,
1389+ data,
1390+ } ) ;
13701391 }
13711392 // Allocates with the GLib allocator
13721393 unsafe {
@@ -1385,7 +1406,7 @@ impl TryFrom<CString> for GString {
13851406 #[ inline]
13861407 fn try_from ( value : CString ) -> Result < Self , Self :: Error > {
13871408 if value. as_bytes ( ) . is_empty ( ) {
1388- Ok ( Self ( Inner :: Native ( None ) ) )
1409+ Ok ( Self :: new ( ) )
13891410 } else {
13901411 // Moves the content of the CString
13911412 // Also check if it's valid UTF-8
@@ -1396,7 +1417,7 @@ impl TryFrom<CString> for GString {
13961417 err,
13971418 )
13981419 } ) ?;
1399- Ok ( Self ( Inner :: Native ( Some ( s. into ( ) ) ) ) )
1420+ Ok ( Self ( Inner :: Native ( s. into ( ) ) ) )
14001421 }
14011422 }
14021423}
@@ -2003,4 +2024,14 @@ mod tests {
20032024 let s = gformat ! ( "bla bla {} bla" , 123 ) ;
20042025 assert_eq ! ( s, "bla bla 123 bla" ) ;
20052026 }
2027+
2028+ #[ test]
2029+ fn layout ( ) {
2030+ // ensure the inline variant is not wider than the other variants
2031+ enum NoInline {
2032+ _Native( Box < str > ) ,
2033+ _Foreign( ptr:: NonNull < c_char > , usize ) ,
2034+ }
2035+ assert_eq ! ( mem:: size_of:: <GString >( ) , mem:: size_of:: <NoInline >( ) ) ;
2036+ }
20062037}
0 commit comments