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