@@ -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`].
@@ -683,11 +696,11 @@ impl GString {
683
696
#[ inline]
684
697
pub unsafe fn from_utf8_unchecked ( mut v : Vec < u8 > ) -> Self {
685
698
if v. is_empty ( ) {
686
- Self ( Inner :: Native ( None ) )
699
+ Self :: new ( )
687
700
} else {
688
701
v. reserve_exact ( 1 ) ;
689
702
v. push ( 0 ) ;
690
- Self ( Inner :: Native ( Some ( String :: from_utf8_unchecked ( v) . into ( ) ) ) )
703
+ Self ( Inner :: Native ( String :: from_utf8_unchecked ( v) . into ( ) ) )
691
704
}
692
705
}
693
706
// rustdoc-stripper-ignore-next
@@ -703,9 +716,9 @@ impl GString {
703
716
return Err ( GStringNoTrailingNulError ( s. into_bytes ( ) ) . into ( ) ) ;
704
717
}
705
718
if s. len ( ) == 1 {
706
- Ok ( Self ( Inner :: Native ( None ) ) )
719
+ Ok ( Self :: new ( ) )
707
720
} else {
708
- Ok ( Self ( Inner :: Native ( Some ( s. into ( ) ) ) ) )
721
+ Ok ( Self ( Inner :: Native ( s. into ( ) ) ) )
709
722
}
710
723
}
711
724
// rustdoc-stripper-ignore-next
@@ -740,9 +753,9 @@ impl GString {
740
753
String :: from_utf8_unchecked ( v)
741
754
} ;
742
755
if s. len ( ) == 1 {
743
- Self ( Inner :: Native ( None ) )
756
+ Self :: new ( )
744
757
} else {
745
- Self ( Inner :: Native ( Some ( s. into ( ) ) ) )
758
+ Self ( Inner :: Native ( s. into ( ) ) )
746
759
}
747
760
}
748
761
// rustdoc-stripper-ignore-next
@@ -758,14 +771,14 @@ impl GString {
758
771
return Err ( GStringNoTrailingNulError ( bytes) . into ( ) ) ;
759
772
} ;
760
773
if nul_pos == 0 {
761
- Ok ( Self ( Inner :: Native ( None ) ) )
774
+ Ok ( Self :: new ( ) )
762
775
} else {
763
776
if let Err ( e) = std:: str:: from_utf8 ( unsafe { bytes. get_unchecked ( ..nul_pos) } ) {
764
777
return Err ( GStringUtf8Error ( bytes, e) . into ( ) ) ;
765
778
}
766
779
bytes. truncate ( nul_pos + 1 ) ;
767
780
let s = unsafe { String :: from_utf8_unchecked ( bytes) } ;
768
- Ok ( Self ( Inner :: Native ( Some ( s. into ( ) ) ) ) )
781
+ Ok ( Self ( Inner :: Native ( s. into ( ) ) ) )
769
782
}
770
783
}
771
784
// rustdoc-stripper-ignore-next
@@ -789,11 +802,11 @@ impl GString {
789
802
#[ inline]
790
803
pub fn from_string_unchecked ( mut s : String ) -> Self {
791
804
if s. is_empty ( ) {
792
- Self ( Inner :: Native ( None ) )
805
+ Self :: new ( )
793
806
} else {
794
807
s. reserve_exact ( 1 ) ;
795
808
s. push ( '\0' ) ;
796
- Self ( Inner :: Native ( Some ( s. into ( ) ) ) )
809
+ Self ( Inner :: Native ( s. into ( ) ) )
797
810
}
798
811
}
799
812
// rustdoc-stripper-ignore-next
@@ -828,9 +841,9 @@ impl GString {
828
841
pub fn as_str ( & self ) -> & str {
829
842
unsafe {
830
843
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 ) ,
833
845
Inner :: Foreign { ptr, len } => ( ptr. as_ptr ( ) as * const u8 , len) ,
846
+ Inner :: Inline { len, ref data } => ( data. as_ptr ( ) , len as usize ) ,
834
847
} ;
835
848
if len == 0 {
836
849
""
@@ -846,12 +859,12 @@ impl GString {
846
859
#[ inline]
847
860
pub fn as_gstr ( & self ) -> & GStr {
848
861
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 ( ) ,
851
863
Inner :: Foreign { len, .. } if len == 0 => & [ 0 ] ,
852
864
Inner :: Foreign { ptr, len } => unsafe {
853
865
slice:: from_raw_parts ( ptr. as_ptr ( ) as * const _ , len + 1 )
854
866
} ,
867
+ Inner :: Inline { len, ref data } => unsafe { data. get_unchecked ( ..len as usize + 1 ) } ,
855
868
} ;
856
869
unsafe { GStr :: from_utf8_with_nul_unchecked ( bytes) }
857
870
}
@@ -861,9 +874,9 @@ impl GString {
861
874
#[ inline]
862
875
pub fn as_ptr ( & self ) -> * const c_char {
863
876
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 _ ,
866
878
Inner :: Foreign { ptr, .. } => ptr. as_ptr ( ) ,
879
+ Inner :: Inline { ref data, .. } => data. as_ptr ( ) as * const _ ,
867
880
}
868
881
}
869
882
@@ -873,34 +886,34 @@ impl GString {
873
886
/// The returned buffer is not guaranteed to contain a trailing nul-byte.
874
887
pub fn into_bytes ( mut self ) -> Vec < u8 > {
875
888
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
+ }
885
895
Inner :: Foreign { ptr, len } => {
886
896
let bytes = unsafe { slice:: from_raw_parts ( ptr. as_ptr ( ) as * const u8 , * len - 1 ) } ;
887
897
bytes. to_owned ( )
888
898
}
899
+ Inner :: Inline { len, data } => {
900
+ unsafe { data. get_unchecked ( ..* len as usize ) } . to_owned ( )
901
+ }
889
902
}
890
903
}
891
904
892
905
// rustdoc-stripper-ignore-next
893
906
/// Consumes the `GString` and returns the underlying byte buffer, with trailing nul-byte.
894
907
pub fn into_bytes_with_nul ( mut self ) -> Vec < u8 > {
895
908
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 ( ) ,
900
910
Inner :: Foreign { ptr, len } => {
901
911
let bytes = unsafe { slice:: from_raw_parts ( ptr. as_ptr ( ) as * const u8 , * len) } ;
902
912
bytes. to_owned ( )
903
913
}
914
+ Inner :: Inline { len, data } => {
915
+ unsafe { data. get_unchecked ( ..* len as usize + 1 ) } . to_owned ( )
916
+ }
904
917
}
905
918
}
906
919
}
@@ -1032,12 +1045,14 @@ impl IntoGlibPtr<*mut c_char> for GString {
1032
1045
/// Transform into a nul-terminated raw C string pointer.
1033
1046
unsafe fn into_glib_ptr ( self ) -> * mut c_char {
1034
1047
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 ( ) ) ,
1037
1049
Inner :: Foreign { ptr, .. } => {
1038
1050
let _s = mem:: ManuallyDrop :: new ( self ) ;
1039
1051
ptr. as_ptr ( )
1040
1052
}
1053
+ Inner :: Inline { len, ref data } => {
1054
+ ffi:: g_strndup ( data. as_ptr ( ) as * const _ , len as usize )
1055
+ }
1041
1056
}
1042
1057
}
1043
1058
}
@@ -1285,22 +1300,22 @@ impl From<GString> for String {
1285
1300
#[ inline]
1286
1301
fn from ( mut s : GString ) -> Self {
1287
1302
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
+ }
1298
1310
Inner :: Foreign { len, .. } if * len == 0 => String :: new ( ) ,
1299
1311
Inner :: Foreign { ptr, len } => unsafe {
1300
1312
// Creates a copy
1301
1313
let slice = slice:: from_raw_parts ( ptr. as_ptr ( ) as * const u8 , * len) ;
1302
1314
std:: str:: from_utf8_unchecked ( slice) . into ( )
1303
1315
} ,
1316
+ Inner :: Inline { len, data } => unsafe {
1317
+ std:: str:: from_utf8_unchecked ( data. get_unchecked ( ..* len as usize ) ) . to_owned ( )
1318
+ } ,
1304
1319
}
1305
1320
}
1306
1321
}
@@ -1357,12 +1372,12 @@ impl From<String> for GString {
1357
1372
GStr :: check_interior_nuls ( & s) . unwrap ( ) ;
1358
1373
}
1359
1374
if s. is_empty ( ) {
1360
- Self ( Inner :: Native ( None ) )
1375
+ Self :: new ( )
1361
1376
} else {
1362
1377
s. reserve_exact ( 1 ) ;
1363
1378
s. push ( '\0' ) ;
1364
1379
// No check for valid UTF-8 here
1365
- Self ( Inner :: Native ( Some ( s. into ( ) ) ) )
1380
+ Self ( Inner :: Native ( s. into ( ) ) )
1366
1381
}
1367
1382
}
1368
1383
}
@@ -1398,8 +1413,14 @@ impl From<&str> for GString {
1398
1413
if cfg ! ( debug_assertions) {
1399
1414
GStr :: check_interior_nuls ( s) . unwrap ( ) ;
1400
1415
}
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
+ } ) ;
1403
1424
}
1404
1425
// Allocates with the GLib allocator
1405
1426
unsafe {
@@ -1418,7 +1439,7 @@ impl TryFrom<CString> for GString {
1418
1439
#[ inline]
1419
1440
fn try_from ( value : CString ) -> Result < Self , Self :: Error > {
1420
1441
if value. as_bytes ( ) . is_empty ( ) {
1421
- Ok ( Self ( Inner :: Native ( None ) ) )
1442
+ Ok ( Self :: new ( ) )
1422
1443
} else {
1423
1444
// Moves the content of the CString
1424
1445
// Also check if it's valid UTF-8
@@ -1429,7 +1450,7 @@ impl TryFrom<CString> for GString {
1429
1450
err,
1430
1451
)
1431
1452
} ) ?;
1432
- Ok ( Self ( Inner :: Native ( Some ( s. into ( ) ) ) ) )
1453
+ Ok ( Self ( Inner :: Native ( s. into ( ) ) ) )
1433
1454
}
1434
1455
}
1435
1456
}
@@ -2094,4 +2115,15 @@ mod tests {
2094
2115
let s = gformat ! ( "bla bla {} bla" , 123 ) ;
2095
2116
assert_eq ! ( s, "bla bla 123 bla" ) ;
2096
2117
}
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
+ }
2097
2129
}
0 commit comments