@@ -454,11 +454,16 @@ impl ToOwned for GStr {
454
454
455
455
#[ inline]
456
456
fn to_owned ( & self ) -> Self :: Owned {
457
- if self . is_empty ( ) {
458
- return GString :: default ( ) ;
459
- }
460
- // Always copy with the GLib allocator
461
457
let b = self . as_bytes_with_nul ( ) ;
458
+ if self . len ( ) < INLINE_LEN {
459
+ let mut data = <[ u8 ; INLINE_LEN ] >:: default ( ) ;
460
+ let b = self . as_bytes ( ) ;
461
+ unsafe { data. get_unchecked_mut ( ..b. len ( ) ) } . copy_from_slice ( b) ;
462
+ return GString ( Inner :: Inline {
463
+ len : self . len ( ) as u8 ,
464
+ data,
465
+ } ) ;
466
+ }
462
467
let inner = unsafe {
463
468
let copy = ffi:: g_strndup ( b. as_ptr ( ) as * const c_char , b. len ( ) ) ;
464
469
Inner :: Foreign {
@@ -609,12 +614,17 @@ const INLINE_LEN: usize =
609
614
/// The constructors beginning with `from_utf8` `and `from_string` can also be used to further
610
615
/// control how interior nul-bytes are handled.
611
616
pub struct GString ( Inner ) ;
617
+
612
618
enum Inner {
613
- Native ( Option < Box < str > > ) ,
619
+ Native ( Box < str > ) ,
614
620
Foreign {
615
621
ptr : ptr:: NonNull < c_char > ,
616
622
len : usize ,
617
623
} ,
624
+ Inline {
625
+ len : u8 ,
626
+ data : [ u8 ; INLINE_LEN ] ,
627
+ } ,
618
628
}
619
629
620
630
unsafe impl Send for GString { }
@@ -627,7 +637,10 @@ impl GString {
627
637
/// Does not allocate.
628
638
#[ inline]
629
639
pub fn new ( ) -> Self {
630
- Self ( Inner :: Native ( None ) )
640
+ Self ( Inner :: Inline {
641
+ len : 0 ,
642
+ data : Default :: default ( ) ,
643
+ } )
631
644
}
632
645
// rustdoc-stripper-ignore-next
633
646
/// Formats an [`Arguments`](std::fmt::Arguments) into a [`GString`].
@@ -691,11 +704,11 @@ impl GString {
691
704
#[ inline]
692
705
pub unsafe fn from_utf8_unchecked ( mut v : Vec < u8 > ) -> Self {
693
706
if v. is_empty ( ) {
694
- Self ( Inner :: Native ( None ) )
707
+ Self :: new ( )
695
708
} else {
696
709
v. reserve_exact ( 1 ) ;
697
710
v. push ( 0 ) ;
698
- Self ( Inner :: Native ( Some ( String :: from_utf8_unchecked ( v) . into ( ) ) ) )
711
+ Self ( Inner :: Native ( String :: from_utf8_unchecked ( v) . into ( ) ) )
699
712
}
700
713
}
701
714
// rustdoc-stripper-ignore-next
@@ -711,9 +724,9 @@ impl GString {
711
724
return Err ( GStringNoTrailingNulError ( s. into_bytes ( ) ) . into ( ) ) ;
712
725
}
713
726
if s. len ( ) == 1 {
714
- Ok ( Self ( Inner :: Native ( None ) ) )
727
+ Ok ( Self :: new ( ) )
715
728
} else {
716
- Ok ( Self ( Inner :: Native ( Some ( s. into ( ) ) ) ) )
729
+ Ok ( Self ( Inner :: Native ( s. into ( ) ) ) )
717
730
}
718
731
}
719
732
// rustdoc-stripper-ignore-next
@@ -748,9 +761,9 @@ impl GString {
748
761
String :: from_utf8_unchecked ( v)
749
762
} ;
750
763
if s. len ( ) == 1 {
751
- Self ( Inner :: Native ( None ) )
764
+ Self :: new ( )
752
765
} else {
753
- Self ( Inner :: Native ( Some ( s. into ( ) ) ) )
766
+ Self ( Inner :: Native ( s. into ( ) ) )
754
767
}
755
768
}
756
769
// rustdoc-stripper-ignore-next
@@ -766,14 +779,14 @@ impl GString {
766
779
return Err ( GStringNoTrailingNulError ( bytes) . into ( ) ) ;
767
780
} ;
768
781
if nul_pos == 0 {
769
- Ok ( Self ( Inner :: Native ( None ) ) )
782
+ Ok ( Self :: new ( ) )
770
783
} else {
771
784
if let Err ( e) = std:: str:: from_utf8 ( unsafe { bytes. get_unchecked ( ..nul_pos) } ) {
772
785
return Err ( GStringUtf8Error ( bytes, e) . into ( ) ) ;
773
786
}
774
787
bytes. truncate ( nul_pos + 1 ) ;
775
788
let s = unsafe { String :: from_utf8_unchecked ( bytes) } ;
776
- Ok ( Self ( Inner :: Native ( Some ( s. into ( ) ) ) ) )
789
+ Ok ( Self ( Inner :: Native ( s. into ( ) ) ) )
777
790
}
778
791
}
779
792
// rustdoc-stripper-ignore-next
@@ -797,11 +810,11 @@ impl GString {
797
810
#[ inline]
798
811
pub fn from_string_unchecked ( mut s : String ) -> Self {
799
812
if s. is_empty ( ) {
800
- Self ( Inner :: Native ( None ) )
813
+ Self :: new ( )
801
814
} else {
802
815
s. reserve_exact ( 1 ) ;
803
816
s. push ( '\0' ) ;
804
- Self ( Inner :: Native ( Some ( s. into ( ) ) ) )
817
+ Self ( Inner :: Native ( s. into ( ) ) )
805
818
}
806
819
}
807
820
// rustdoc-stripper-ignore-next
@@ -836,9 +849,9 @@ impl GString {
836
849
pub fn as_str ( & self ) -> & str {
837
850
unsafe {
838
851
let ( ptr, len) = match self . 0 {
839
- Inner :: Native ( None ) => ( ptr:: null ( ) , 0 ) ,
840
- Inner :: Native ( Some ( ref s) ) => ( s. as_ptr ( ) as * const u8 , s. len ( ) - 1 ) ,
852
+ Inner :: Native ( ref s) => ( s. as_ptr ( ) as * const u8 , s. len ( ) - 1 ) ,
841
853
Inner :: Foreign { ptr, len } => ( ptr. as_ptr ( ) as * const u8 , len) ,
854
+ Inner :: Inline { len, ref data } => ( data. as_ptr ( ) , len as usize ) ,
842
855
} ;
843
856
if len == 0 {
844
857
""
@@ -854,12 +867,12 @@ impl GString {
854
867
#[ inline]
855
868
pub fn as_gstr ( & self ) -> & GStr {
856
869
let bytes = match self . 0 {
857
- Inner :: Native ( None ) => return <& GStr >:: default ( ) ,
858
- Inner :: Native ( Some ( ref s) ) => s. as_bytes ( ) ,
870
+ Inner :: Native ( ref s) => s. as_bytes ( ) ,
859
871
Inner :: Foreign { len, .. } if len == 0 => & [ 0 ] ,
860
872
Inner :: Foreign { ptr, len } => unsafe {
861
873
slice:: from_raw_parts ( ptr. as_ptr ( ) as * const _ , len + 1 )
862
874
} ,
875
+ Inner :: Inline { len, ref data } => unsafe { data. get_unchecked ( ..len as usize + 1 ) } ,
863
876
} ;
864
877
unsafe { GStr :: from_utf8_with_nul_unchecked ( bytes) }
865
878
}
@@ -869,9 +882,9 @@ impl GString {
869
882
#[ inline]
870
883
pub fn as_ptr ( & self ) -> * const c_char {
871
884
match self . 0 {
872
- Inner :: Native ( None ) => <& GStr >:: default ( ) . as_ptr ( ) ,
873
- Inner :: Native ( Some ( ref s) ) => s. as_ptr ( ) as * const _ ,
885
+ Inner :: Native ( ref s) => s. as_ptr ( ) as * const _ ,
874
886
Inner :: Foreign { ptr, .. } => ptr. as_ptr ( ) ,
887
+ Inner :: Inline { ref data, .. } => data. as_ptr ( ) as * const _ ,
875
888
}
876
889
}
877
890
@@ -881,34 +894,34 @@ impl GString {
881
894
/// The returned buffer is not guaranteed to contain a trailing nul-byte.
882
895
pub fn into_bytes ( mut self ) -> Vec < u8 > {
883
896
match & mut self . 0 {
884
- Inner :: Native ( s) => match s. take ( ) {
885
- None => Vec :: new ( ) ,
886
- Some ( s) => {
887
- let mut s = String :: from ( s) ;
888
- let _nul = s. pop ( ) ;
889
- debug_assert_eq ! ( _nul, Some ( '\0' ) ) ;
890
- s. into_bytes ( )
891
- }
892
- } ,
897
+ Inner :: Native ( s) => {
898
+ let mut s = String :: from ( mem:: replace ( s, "" . into ( ) ) ) ;
899
+ let _nul = s. pop ( ) ;
900
+ debug_assert_eq ! ( _nul, Some ( '\0' ) ) ;
901
+ s. into_bytes ( )
902
+ }
893
903
Inner :: Foreign { ptr, len } => {
894
904
let bytes = unsafe { slice:: from_raw_parts ( ptr. as_ptr ( ) as * const u8 , * len - 1 ) } ;
895
905
bytes. to_owned ( )
896
906
}
907
+ Inner :: Inline { len, data } => {
908
+ unsafe { data. get_unchecked ( ..* len as usize ) } . to_owned ( )
909
+ }
897
910
}
898
911
}
899
912
900
913
// rustdoc-stripper-ignore-next
901
914
/// Consumes the `GString` and returns the underlying byte buffer, with trailing nul-byte.
902
915
pub fn into_bytes_with_nul ( mut self ) -> Vec < u8 > {
903
916
match & mut self . 0 {
904
- Inner :: Native ( s) => match s. take ( ) {
905
- None => vec ! [ 0u8 ] ,
906
- Some ( s) => str:: into_boxed_bytes ( s) . into ( ) ,
907
- } ,
917
+ Inner :: Native ( s) => str:: into_boxed_bytes ( mem:: replace ( s, "" . into ( ) ) ) . into ( ) ,
908
918
Inner :: Foreign { ptr, len } => {
909
919
let bytes = unsafe { slice:: from_raw_parts ( ptr. as_ptr ( ) as * const u8 , * len) } ;
910
920
bytes. to_owned ( )
911
921
}
922
+ Inner :: Inline { len, data } => {
923
+ unsafe { data. get_unchecked ( ..* len as usize + 1 ) } . to_owned ( )
924
+ }
912
925
}
913
926
}
914
927
}
@@ -1040,12 +1053,14 @@ impl IntoGlibPtr<*mut c_char> for GString {
1040
1053
/// Transform into a nul-terminated raw C string pointer.
1041
1054
unsafe fn into_glib_ptr ( self ) -> * mut c_char {
1042
1055
match self . 0 {
1043
- Inner :: Native ( None ) => ffi:: g_malloc0 ( 1 ) as * mut _ ,
1044
- Inner :: Native ( Some ( ref s) ) => ffi:: g_strndup ( s. as_ptr ( ) as * const _ , s. len ( ) ) ,
1056
+ Inner :: Native ( ref s) => ffi:: g_strndup ( s. as_ptr ( ) as * const _ , s. len ( ) ) ,
1045
1057
Inner :: Foreign { ptr, .. } => {
1046
1058
let _s = mem:: ManuallyDrop :: new ( self ) ;
1047
1059
ptr. as_ptr ( )
1048
1060
}
1061
+ Inner :: Inline { len, ref data } => {
1062
+ ffi:: g_strndup ( data. as_ptr ( ) as * const _ , len as usize )
1063
+ }
1049
1064
}
1050
1065
}
1051
1066
}
@@ -1293,22 +1308,22 @@ impl From<GString> for String {
1293
1308
#[ inline]
1294
1309
fn from ( mut s : GString ) -> Self {
1295
1310
match & mut s. 0 {
1296
- Inner :: Native ( s) => match s. take ( ) {
1297
- None => Self :: default ( ) ,
1298
- Some ( s) => {
1299
- // Moves the underlying string
1300
- let mut s = String :: from ( s) ;
1301
- let _nul = s. pop ( ) ;
1302
- debug_assert_eq ! ( _nul, Some ( '\0' ) ) ;
1303
- s
1304
- }
1305
- } ,
1311
+ Inner :: Native ( s) => {
1312
+ // Moves the underlying string
1313
+ let mut s = String :: from ( mem:: replace ( s, "" . into ( ) ) ) ;
1314
+ let _nul = s. pop ( ) ;
1315
+ debug_assert_eq ! ( _nul, Some ( '\0' ) ) ;
1316
+ s
1317
+ }
1306
1318
Inner :: Foreign { len, .. } if * len == 0 => String :: new ( ) ,
1307
1319
Inner :: Foreign { ptr, len } => unsafe {
1308
1320
// Creates a copy
1309
1321
let slice = slice:: from_raw_parts ( ptr. as_ptr ( ) as * const u8 , * len) ;
1310
1322
std:: str:: from_utf8_unchecked ( slice) . into ( )
1311
1323
} ,
1324
+ Inner :: Inline { len, data } => unsafe {
1325
+ std:: str:: from_utf8_unchecked ( data. get_unchecked ( ..* len as usize ) ) . to_owned ( )
1326
+ } ,
1312
1327
}
1313
1328
}
1314
1329
}
@@ -1365,12 +1380,12 @@ impl From<String> for GString {
1365
1380
GStr :: check_interior_nuls ( & s) . unwrap ( ) ;
1366
1381
}
1367
1382
if s. is_empty ( ) {
1368
- Self ( Inner :: Native ( None ) )
1383
+ Self :: new ( )
1369
1384
} else {
1370
1385
s. reserve_exact ( 1 ) ;
1371
1386
s. push ( '\0' ) ;
1372
1387
// No check for valid UTF-8 here
1373
- Self ( Inner :: Native ( Some ( s. into ( ) ) ) )
1388
+ Self ( Inner :: Native ( s. into ( ) ) )
1374
1389
}
1375
1390
}
1376
1391
}
@@ -1406,8 +1421,14 @@ impl From<&str> for GString {
1406
1421
if cfg ! ( debug_assertions) {
1407
1422
GStr :: check_interior_nuls ( s) . unwrap ( ) ;
1408
1423
}
1409
- if s. is_empty ( ) {
1410
- return Self :: default ( ) ;
1424
+ if s. len ( ) < INLINE_LEN {
1425
+ let mut data = <[ u8 ; INLINE_LEN ] >:: default ( ) ;
1426
+ let b = s. as_bytes ( ) ;
1427
+ unsafe { data. get_unchecked_mut ( ..b. len ( ) ) } . copy_from_slice ( b) ;
1428
+ return Self ( Inner :: Inline {
1429
+ len : b. len ( ) as u8 ,
1430
+ data,
1431
+ } ) ;
1411
1432
}
1412
1433
// Allocates with the GLib allocator
1413
1434
unsafe {
@@ -1426,7 +1447,7 @@ impl TryFrom<CString> for GString {
1426
1447
#[ inline]
1427
1448
fn try_from ( value : CString ) -> Result < Self , Self :: Error > {
1428
1449
if value. as_bytes ( ) . is_empty ( ) {
1429
- Ok ( Self ( Inner :: Native ( None ) ) )
1450
+ Ok ( Self :: new ( ) )
1430
1451
} else {
1431
1452
// Moves the content of the CString
1432
1453
// Also check if it's valid UTF-8
@@ -1437,7 +1458,7 @@ impl TryFrom<CString> for GString {
1437
1458
err,
1438
1459
)
1439
1460
} ) ?;
1440
- Ok ( Self ( Inner :: Native ( Some ( s. into ( ) ) ) ) )
1461
+ Ok ( Self ( Inner :: Native ( s. into ( ) ) ) )
1441
1462
}
1442
1463
}
1443
1464
}
@@ -2102,4 +2123,15 @@ mod tests {
2102
2123
let s = gformat ! ( "bla bla {} bla" , 123 ) ;
2103
2124
assert_eq ! ( s, "bla bla 123 bla" ) ;
2104
2125
}
2126
+
2127
+ #[ test]
2128
+ fn layout ( ) {
2129
+ // ensure the inline variant is not wider than the other variants
2130
+ enum NoInline {
2131
+ _Native( Box < str > ) ,
2132
+ _Foreign( ptr:: NonNull < c_char > , usize ) ,
2133
+ }
2134
+ assert_eq ! ( mem:: size_of:: <GString >( ) , mem:: size_of:: <NoInline >( ) ) ;
2135
+ assert_eq ! ( mem:: size_of:: <GString >( ) , mem:: size_of:: <String >( ) ) ;
2136
+ }
2105
2137
}
0 commit comments