@@ -438,11 +438,16 @@ impl ToOwned for GStr {
438
438
439
439
#[ inline]
440
440
fn to_owned ( & self ) -> Self :: Owned {
441
- if self . is_empty ( ) {
442
- return GString :: default ( ) ;
443
- }
444
- // Always copy with the GLib allocator
445
441
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
+ }
446
451
let inner = unsafe {
447
452
let copy = ffi:: g_strndup ( b. as_ptr ( ) as * const c_char , b. len ( ) ) ;
448
453
Inner :: Foreign {
@@ -594,12 +599,17 @@ const INLINE_LEN: usize =
594
599
/// control how interior nul-bytes are handled.
595
600
#[ repr( transparent) ]
596
601
pub struct GString ( Inner ) ;
602
+
597
603
enum Inner {
598
- Native ( Option < Box < str > > ) ,
604
+ Native ( Box < str > ) ,
599
605
Foreign {
600
606
ptr : ptr:: NonNull < c_char > ,
601
607
len : usize ,
602
608
} ,
609
+ Inline {
610
+ len : u8 ,
611
+ data : [ u8 ; INLINE_LEN ] ,
612
+ } ,
603
613
}
604
614
605
615
unsafe impl Send for GString { }
@@ -612,7 +622,10 @@ impl GString {
612
622
/// Does not allocate.
613
623
#[ inline]
614
624
pub fn new ( ) -> Self {
615
- Self ( Inner :: Native ( None ) )
625
+ Self ( Inner :: Inline {
626
+ len : 0 ,
627
+ data : Default :: default ( ) ,
628
+ } )
616
629
}
617
630
// rustdoc-stripper-ignore-next
618
631
/// Formats an [`Arguments`](std::fmt::Arguments) into a [`GString`].
@@ -676,11 +689,11 @@ impl GString {
676
689
#[ inline]
677
690
pub unsafe fn from_utf8_unchecked ( mut v : Vec < u8 > ) -> Self {
678
691
if v. is_empty ( ) {
679
- Self ( Inner :: Native ( None ) )
692
+ Self :: new ( )
680
693
} else {
681
694
v. reserve_exact ( 1 ) ;
682
695
v. push ( 0 ) ;
683
- Self ( Inner :: Native ( Some ( String :: from_utf8_unchecked ( v) . into ( ) ) ) )
696
+ Self ( Inner :: Native ( String :: from_utf8_unchecked ( v) . into ( ) ) )
684
697
}
685
698
}
686
699
// rustdoc-stripper-ignore-next
@@ -696,9 +709,9 @@ impl GString {
696
709
return Err ( GStringNoTrailingNulError ( s. into_bytes ( ) ) . into ( ) ) ;
697
710
}
698
711
if s. len ( ) == 1 {
699
- Ok ( Self ( Inner :: Native ( None ) ) )
712
+ Ok ( Self :: new ( ) )
700
713
} else {
701
- Ok ( Self ( Inner :: Native ( Some ( s. into ( ) ) ) ) )
714
+ Ok ( Self ( Inner :: Native ( s. into ( ) ) ) )
702
715
}
703
716
}
704
717
// rustdoc-stripper-ignore-next
@@ -733,9 +746,9 @@ impl GString {
733
746
String :: from_utf8_unchecked ( v)
734
747
} ;
735
748
if s. len ( ) == 1 {
736
- Self ( Inner :: Native ( None ) )
749
+ Self :: new ( )
737
750
} else {
738
- Self ( Inner :: Native ( Some ( s. into ( ) ) ) )
751
+ Self ( Inner :: Native ( s. into ( ) ) )
739
752
}
740
753
}
741
754
// rustdoc-stripper-ignore-next
@@ -751,14 +764,14 @@ impl GString {
751
764
return Err ( GStringNoTrailingNulError ( bytes) . into ( ) ) ;
752
765
} ;
753
766
if nul_pos == 0 {
754
- Ok ( Self ( Inner :: Native ( None ) ) )
767
+ Ok ( Self :: new ( ) )
755
768
} else {
756
769
if let Err ( e) = std:: str:: from_utf8 ( unsafe { bytes. get_unchecked ( ..nul_pos) } ) {
757
770
return Err ( GStringUtf8Error ( bytes, e) . into ( ) ) ;
758
771
}
759
772
bytes. truncate ( nul_pos + 1 ) ;
760
773
let s = unsafe { String :: from_utf8_unchecked ( bytes) } ;
761
- Ok ( Self ( Inner :: Native ( Some ( s. into ( ) ) ) ) )
774
+ Ok ( Self ( Inner :: Native ( s. into ( ) ) ) )
762
775
}
763
776
}
764
777
// rustdoc-stripper-ignore-next
@@ -782,11 +795,11 @@ impl GString {
782
795
#[ inline]
783
796
pub fn from_string_unchecked ( mut s : String ) -> Self {
784
797
if s. is_empty ( ) {
785
- Self ( Inner :: Native ( None ) )
798
+ Self :: new ( )
786
799
} else {
787
800
s. reserve_exact ( 1 ) ;
788
801
s. push ( '\0' ) ;
789
- Self ( Inner :: Native ( Some ( s. into ( ) ) ) )
802
+ Self ( Inner :: Native ( s. into ( ) ) )
790
803
}
791
804
}
792
805
// rustdoc-stripper-ignore-next
@@ -795,9 +808,9 @@ impl GString {
795
808
pub fn as_str ( & self ) -> & str {
796
809
unsafe {
797
810
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 ) ,
800
812
Inner :: Foreign { ptr, len } => ( ptr. as_ptr ( ) as * const u8 , len) ,
813
+ Inner :: Inline { len, ref data } => ( data. as_ptr ( ) , len as usize ) ,
801
814
} ;
802
815
if len == 0 {
803
816
""
@@ -813,12 +826,12 @@ impl GString {
813
826
#[ inline]
814
827
pub fn as_gstr ( & self ) -> & GStr {
815
828
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 ( ) ,
818
830
Inner :: Foreign { len, .. } if len == 0 => & [ 0 ] ,
819
831
Inner :: Foreign { ptr, len } => unsafe {
820
832
slice:: from_raw_parts ( ptr. as_ptr ( ) as * const _ , len + 1 )
821
833
} ,
834
+ Inner :: Inline { len, ref data } => unsafe { data. get_unchecked ( ..len as usize + 1 ) } ,
822
835
} ;
823
836
unsafe { GStr :: from_utf8_with_nul_unchecked ( bytes) }
824
837
}
@@ -828,9 +841,9 @@ impl GString {
828
841
#[ inline]
829
842
pub fn as_ptr ( & self ) -> * const c_char {
830
843
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 _ ,
833
845
Inner :: Foreign { ptr, .. } => ptr. as_ptr ( ) ,
846
+ Inner :: Inline { ref data, .. } => data. as_ptr ( ) as * const _ ,
834
847
}
835
848
}
836
849
@@ -840,34 +853,34 @@ impl GString {
840
853
/// The returned buffer is not guaranteed to contain a trailing nul-byte.
841
854
pub fn into_bytes ( mut self ) -> Vec < u8 > {
842
855
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
+ }
852
862
Inner :: Foreign { ptr, len } => {
853
863
let bytes = unsafe { slice:: from_raw_parts ( ptr. as_ptr ( ) as * const u8 , * len - 1 ) } ;
854
864
bytes. to_owned ( )
855
865
}
866
+ Inner :: Inline { len, data } => {
867
+ unsafe { data. get_unchecked ( ..* len as usize ) } . to_owned ( )
868
+ }
856
869
}
857
870
}
858
871
859
872
// rustdoc-stripper-ignore-next
860
873
/// Consumes the `GString` and returns the underlying byte buffer, with trailing nul-byte.
861
874
pub fn into_bytes_with_nul ( mut self ) -> Vec < u8 > {
862
875
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 ( ) ,
867
877
Inner :: Foreign { ptr, len } => {
868
878
let bytes = unsafe { slice:: from_raw_parts ( ptr. as_ptr ( ) as * const u8 , * len) } ;
869
879
bytes. to_owned ( )
870
880
}
881
+ Inner :: Inline { len, data } => {
882
+ unsafe { data. get_unchecked ( ..* len as usize + 1 ) } . to_owned ( )
883
+ }
871
884
}
872
885
}
873
886
}
@@ -999,12 +1012,14 @@ impl IntoGlibPtr<*mut c_char> for GString {
999
1012
/// Transform into a nul-terminated raw C string pointer.
1000
1013
unsafe fn into_glib_ptr ( self ) -> * mut c_char {
1001
1014
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 ( ) ) ,
1004
1016
Inner :: Foreign { ptr, .. } => {
1005
1017
let _s = mem:: ManuallyDrop :: new ( self ) ;
1006
1018
ptr. as_ptr ( )
1007
1019
}
1020
+ Inner :: Inline { len, ref data } => {
1021
+ ffi:: g_strndup ( data. as_ptr ( ) as * const _ , len as usize )
1022
+ }
1008
1023
}
1009
1024
}
1010
1025
}
@@ -1252,22 +1267,22 @@ impl From<GString> for String {
1252
1267
#[ inline]
1253
1268
fn from ( mut s : GString ) -> Self {
1254
1269
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
+ }
1265
1277
Inner :: Foreign { len, .. } if * len == 0 => String :: new ( ) ,
1266
1278
Inner :: Foreign { ptr, len } => unsafe {
1267
1279
// Creates a copy
1268
1280
let slice = slice:: from_raw_parts ( ptr. as_ptr ( ) as * const u8 , * len) ;
1269
1281
std:: str:: from_utf8_unchecked ( slice) . into ( )
1270
1282
} ,
1283
+ Inner :: Inline { len, data } => unsafe {
1284
+ std:: str:: from_utf8_unchecked ( data. get_unchecked ( ..* len as usize ) ) . to_owned ( )
1285
+ } ,
1271
1286
}
1272
1287
}
1273
1288
}
@@ -1324,12 +1339,12 @@ impl From<String> for GString {
1324
1339
GStr :: check_interior_nuls ( & s) . unwrap ( ) ;
1325
1340
}
1326
1341
if s. is_empty ( ) {
1327
- Self ( Inner :: Native ( None ) )
1342
+ Self :: new ( )
1328
1343
} else {
1329
1344
s. reserve_exact ( 1 ) ;
1330
1345
s. push ( '\0' ) ;
1331
1346
// No check for valid UTF-8 here
1332
- Self ( Inner :: Native ( Some ( s. into ( ) ) ) )
1347
+ Self ( Inner :: Native ( s. into ( ) ) )
1333
1348
}
1334
1349
}
1335
1350
}
@@ -1365,8 +1380,14 @@ impl From<&str> for GString {
1365
1380
if cfg ! ( debug_assertions) {
1366
1381
GStr :: check_interior_nuls ( s) . unwrap ( ) ;
1367
1382
}
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
+ } ) ;
1370
1391
}
1371
1392
// Allocates with the GLib allocator
1372
1393
unsafe {
@@ -1385,7 +1406,7 @@ impl TryFrom<CString> for GString {
1385
1406
#[ inline]
1386
1407
fn try_from ( value : CString ) -> Result < Self , Self :: Error > {
1387
1408
if value. as_bytes ( ) . is_empty ( ) {
1388
- Ok ( Self ( Inner :: Native ( None ) ) )
1409
+ Ok ( Self :: new ( ) )
1389
1410
} else {
1390
1411
// Moves the content of the CString
1391
1412
// Also check if it's valid UTF-8
@@ -1396,7 +1417,7 @@ impl TryFrom<CString> for GString {
1396
1417
err,
1397
1418
)
1398
1419
} ) ?;
1399
- Ok ( Self ( Inner :: Native ( Some ( s. into ( ) ) ) ) )
1420
+ Ok ( Self ( Inner :: Native ( s. into ( ) ) ) )
1400
1421
}
1401
1422
}
1402
1423
}
@@ -2003,4 +2024,14 @@ mod tests {
2003
2024
let s = gformat ! ( "bla bla {} bla" , 123 ) ;
2004
2025
assert_eq ! ( s, "bla bla 123 bla" ) ;
2005
2026
}
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
+ }
2006
2037
}
0 commit comments