@@ -448,11 +448,16 @@ impl ToOwned for GStr {
448
448
449
449
#[ inline]
450
450
fn to_owned ( & self ) -> Self :: Owned {
451
- if self . is_empty ( ) {
452
- return GString :: default ( ) ;
453
- }
454
- // Always copy with the GLib allocator
455
451
let b = self . as_bytes_with_nul ( ) ;
452
+ if self . len ( ) < INLINE_LEN {
453
+ let mut data = <[ u8 ; INLINE_LEN ] >:: default ( ) ;
454
+ let b = self . as_bytes ( ) ;
455
+ unsafe { data. get_unchecked_mut ( ..b. len ( ) ) } . copy_from_slice ( b) ;
456
+ return GString ( Inner :: Inline {
457
+ len : self . len ( ) as u8 ,
458
+ data,
459
+ } ) ;
460
+ }
456
461
let inner = unsafe {
457
462
let copy = ffi:: g_strndup ( b. as_ptr ( ) as * const c_char , b. len ( ) ) ;
458
463
Inner :: Foreign {
@@ -603,12 +608,17 @@ const INLINE_LEN: usize =
603
608
/// The constructors beginning with `from_utf8` `and `from_string` can also be used to further
604
609
/// control how interior nul-bytes are handled.
605
610
pub struct GString ( Inner ) ;
611
+
606
612
enum Inner {
607
- Native ( Option < Box < str > > ) ,
613
+ Native ( Box < str > ) ,
608
614
Foreign {
609
615
ptr : ptr:: NonNull < c_char > ,
610
616
len : usize ,
611
617
} ,
618
+ Inline {
619
+ len : u8 ,
620
+ data : [ u8 ; INLINE_LEN ] ,
621
+ } ,
612
622
}
613
623
614
624
unsafe impl Send for GString { }
@@ -621,7 +631,10 @@ impl GString {
621
631
/// Does not allocate.
622
632
#[ inline]
623
633
pub fn new ( ) -> Self {
624
- Self ( Inner :: Native ( None ) )
634
+ Self ( Inner :: Inline {
635
+ len : 0 ,
636
+ data : Default :: default ( ) ,
637
+ } )
625
638
}
626
639
// rustdoc-stripper-ignore-next
627
640
/// Formats an [`Arguments`](std::fmt::Arguments) into a [`GString`].
@@ -685,11 +698,11 @@ impl GString {
685
698
#[ inline]
686
699
pub unsafe fn from_utf8_unchecked ( mut v : Vec < u8 > ) -> Self {
687
700
if v. is_empty ( ) {
688
- Self ( Inner :: Native ( None ) )
701
+ Self :: new ( )
689
702
} else {
690
703
v. reserve_exact ( 1 ) ;
691
704
v. push ( 0 ) ;
692
- Self ( Inner :: Native ( Some ( String :: from_utf8_unchecked ( v) . into ( ) ) ) )
705
+ Self ( Inner :: Native ( String :: from_utf8_unchecked ( v) . into ( ) ) )
693
706
}
694
707
}
695
708
// rustdoc-stripper-ignore-next
@@ -705,9 +718,9 @@ impl GString {
705
718
return Err ( GStringNoTrailingNulError ( s. into_bytes ( ) ) . into ( ) ) ;
706
719
}
707
720
if s. len ( ) == 1 {
708
- Ok ( Self ( Inner :: Native ( None ) ) )
721
+ Ok ( Self :: new ( ) )
709
722
} else {
710
- Ok ( Self ( Inner :: Native ( Some ( s. into ( ) ) ) ) )
723
+ Ok ( Self ( Inner :: Native ( s. into ( ) ) ) )
711
724
}
712
725
}
713
726
// rustdoc-stripper-ignore-next
@@ -742,9 +755,9 @@ impl GString {
742
755
String :: from_utf8_unchecked ( v)
743
756
} ;
744
757
if s. len ( ) == 1 {
745
- Self ( Inner :: Native ( None ) )
758
+ Self :: new ( )
746
759
} else {
747
- Self ( Inner :: Native ( Some ( s. into ( ) ) ) )
760
+ Self ( Inner :: Native ( s. into ( ) ) )
748
761
}
749
762
}
750
763
// rustdoc-stripper-ignore-next
@@ -760,14 +773,14 @@ impl GString {
760
773
return Err ( GStringNoTrailingNulError ( bytes) . into ( ) ) ;
761
774
} ;
762
775
if nul_pos == 0 {
763
- Ok ( Self ( Inner :: Native ( None ) ) )
776
+ Ok ( Self :: new ( ) )
764
777
} else {
765
778
if let Err ( e) = std:: str:: from_utf8 ( unsafe { bytes. get_unchecked ( ..nul_pos) } ) {
766
779
return Err ( GStringUtf8Error ( bytes, e) . into ( ) ) ;
767
780
}
768
781
bytes. truncate ( nul_pos + 1 ) ;
769
782
let s = unsafe { String :: from_utf8_unchecked ( bytes) } ;
770
- Ok ( Self ( Inner :: Native ( Some ( s. into ( ) ) ) ) )
783
+ Ok ( Self ( Inner :: Native ( s. into ( ) ) ) )
771
784
}
772
785
}
773
786
// rustdoc-stripper-ignore-next
@@ -791,11 +804,11 @@ impl GString {
791
804
#[ inline]
792
805
pub fn from_string_unchecked ( mut s : String ) -> Self {
793
806
if s. is_empty ( ) {
794
- Self ( Inner :: Native ( None ) )
807
+ Self :: new ( )
795
808
} else {
796
809
s. reserve_exact ( 1 ) ;
797
810
s. push ( '\0' ) ;
798
- Self ( Inner :: Native ( Some ( s. into ( ) ) ) )
811
+ Self ( Inner :: Native ( s. into ( ) ) )
799
812
}
800
813
}
801
814
// rustdoc-stripper-ignore-next
@@ -830,9 +843,9 @@ impl GString {
830
843
pub fn as_str ( & self ) -> & str {
831
844
unsafe {
832
845
let ( ptr, len) = match self . 0 {
833
- Inner :: Native ( None ) => ( ptr:: null ( ) , 0 ) ,
834
- Inner :: Native ( Some ( ref s) ) => ( s. as_ptr ( ) as * const u8 , s. len ( ) - 1 ) ,
846
+ Inner :: Native ( ref s) => ( s. as_ptr ( ) as * const u8 , s. len ( ) - 1 ) ,
835
847
Inner :: Foreign { ptr, len } => ( ptr. as_ptr ( ) as * const u8 , len) ,
848
+ Inner :: Inline { len, ref data } => ( data. as_ptr ( ) , len as usize ) ,
836
849
} ;
837
850
if len == 0 {
838
851
""
@@ -848,12 +861,12 @@ impl GString {
848
861
#[ inline]
849
862
pub fn as_gstr ( & self ) -> & GStr {
850
863
let bytes = match self . 0 {
851
- Inner :: Native ( None ) => return <& GStr >:: default ( ) ,
852
- Inner :: Native ( Some ( ref s) ) => s. as_bytes ( ) ,
864
+ Inner :: Native ( ref s) => s. as_bytes ( ) ,
853
865
Inner :: Foreign { len, .. } if len == 0 => & [ 0 ] ,
854
866
Inner :: Foreign { ptr, len } => unsafe {
855
867
slice:: from_raw_parts ( ptr. as_ptr ( ) as * const _ , len + 1 )
856
868
} ,
869
+ Inner :: Inline { len, ref data } => unsafe { data. get_unchecked ( ..len as usize + 1 ) } ,
857
870
} ;
858
871
unsafe { GStr :: from_utf8_with_nul_unchecked ( bytes) }
859
872
}
@@ -863,9 +876,9 @@ impl GString {
863
876
#[ inline]
864
877
pub fn as_ptr ( & self ) -> * const c_char {
865
878
match self . 0 {
866
- Inner :: Native ( None ) => <& GStr >:: default ( ) . as_ptr ( ) ,
867
- Inner :: Native ( Some ( ref s) ) => s. as_ptr ( ) as * const _ ,
879
+ Inner :: Native ( ref s) => s. as_ptr ( ) as * const _ ,
868
880
Inner :: Foreign { ptr, .. } => ptr. as_ptr ( ) ,
881
+ Inner :: Inline { ref data, .. } => data. as_ptr ( ) as * const _ ,
869
882
}
870
883
}
871
884
@@ -875,34 +888,34 @@ impl GString {
875
888
/// The returned buffer is not guaranteed to contain a trailing nul-byte.
876
889
pub fn into_bytes ( mut self ) -> Vec < u8 > {
877
890
match & mut self . 0 {
878
- Inner :: Native ( s) => match s. take ( ) {
879
- None => Vec :: new ( ) ,
880
- Some ( s) => {
881
- let mut s = String :: from ( s) ;
882
- let _nul = s. pop ( ) ;
883
- debug_assert_eq ! ( _nul, Some ( '\0' ) ) ;
884
- s. into_bytes ( )
885
- }
886
- } ,
891
+ Inner :: Native ( s) => {
892
+ let mut s = String :: from ( mem:: replace ( s, "" . into ( ) ) ) ;
893
+ let _nul = s. pop ( ) ;
894
+ debug_assert_eq ! ( _nul, Some ( '\0' ) ) ;
895
+ s. into_bytes ( )
896
+ }
887
897
Inner :: Foreign { ptr, len } => {
888
898
let bytes = unsafe { slice:: from_raw_parts ( ptr. as_ptr ( ) as * const u8 , * len - 1 ) } ;
889
899
bytes. to_owned ( )
890
900
}
901
+ Inner :: Inline { len, data } => {
902
+ unsafe { data. get_unchecked ( ..* len as usize ) } . to_owned ( )
903
+ }
891
904
}
892
905
}
893
906
894
907
// rustdoc-stripper-ignore-next
895
908
/// Consumes the `GString` and returns the underlying byte buffer, with trailing nul-byte.
896
909
pub fn into_bytes_with_nul ( mut self ) -> Vec < u8 > {
897
910
match & mut self . 0 {
898
- Inner :: Native ( s) => match s. take ( ) {
899
- None => vec ! [ 0u8 ] ,
900
- Some ( s) => str:: into_boxed_bytes ( s) . into ( ) ,
901
- } ,
911
+ Inner :: Native ( s) => str:: into_boxed_bytes ( mem:: replace ( s, "" . into ( ) ) ) . into ( ) ,
902
912
Inner :: Foreign { ptr, len } => {
903
913
let bytes = unsafe { slice:: from_raw_parts ( ptr. as_ptr ( ) as * const u8 , * len) } ;
904
914
bytes. to_owned ( )
905
915
}
916
+ Inner :: Inline { len, data } => {
917
+ unsafe { data. get_unchecked ( ..* len as usize + 1 ) } . to_owned ( )
918
+ }
906
919
}
907
920
}
908
921
}
@@ -1034,12 +1047,14 @@ impl IntoGlibPtr<*mut c_char> for GString {
1034
1047
/// Transform into a nul-terminated raw C string pointer.
1035
1048
unsafe fn into_glib_ptr ( self ) -> * mut c_char {
1036
1049
match self . 0 {
1037
- Inner :: Native ( None ) => ffi:: g_malloc0 ( 1 ) as * mut _ ,
1038
- Inner :: Native ( Some ( ref s) ) => ffi:: g_strndup ( s. as_ptr ( ) as * const _ , s. len ( ) ) ,
1050
+ Inner :: Native ( ref s) => ffi:: g_strndup ( s. as_ptr ( ) as * const _ , s. len ( ) ) ,
1039
1051
Inner :: Foreign { ptr, .. } => {
1040
1052
let _s = mem:: ManuallyDrop :: new ( self ) ;
1041
1053
ptr. as_ptr ( )
1042
1054
}
1055
+ Inner :: Inline { len, ref data } => {
1056
+ ffi:: g_strndup ( data. as_ptr ( ) as * const _ , len as usize )
1057
+ }
1043
1058
}
1044
1059
}
1045
1060
}
@@ -1287,22 +1302,22 @@ impl From<GString> for String {
1287
1302
#[ inline]
1288
1303
fn from ( mut s : GString ) -> Self {
1289
1304
match & mut s. 0 {
1290
- Inner :: Native ( s) => match s. take ( ) {
1291
- None => Self :: default ( ) ,
1292
- Some ( s) => {
1293
- // Moves the underlying string
1294
- let mut s = String :: from ( s) ;
1295
- let _nul = s. pop ( ) ;
1296
- debug_assert_eq ! ( _nul, Some ( '\0' ) ) ;
1297
- s
1298
- }
1299
- } ,
1305
+ Inner :: Native ( s) => {
1306
+ // Moves the underlying string
1307
+ let mut s = String :: from ( mem:: replace ( s, "" . into ( ) ) ) ;
1308
+ let _nul = s. pop ( ) ;
1309
+ debug_assert_eq ! ( _nul, Some ( '\0' ) ) ;
1310
+ s
1311
+ }
1300
1312
Inner :: Foreign { len, .. } if * len == 0 => String :: new ( ) ,
1301
1313
Inner :: Foreign { ptr, len } => unsafe {
1302
1314
// Creates a copy
1303
1315
let slice = slice:: from_raw_parts ( ptr. as_ptr ( ) as * const u8 , * len) ;
1304
1316
std:: str:: from_utf8_unchecked ( slice) . into ( )
1305
1317
} ,
1318
+ Inner :: Inline { len, data } => unsafe {
1319
+ std:: str:: from_utf8_unchecked ( data. get_unchecked ( ..* len as usize ) ) . to_owned ( )
1320
+ } ,
1306
1321
}
1307
1322
}
1308
1323
}
@@ -1359,12 +1374,12 @@ impl From<String> for GString {
1359
1374
GStr :: check_interior_nuls ( & s) . unwrap ( ) ;
1360
1375
}
1361
1376
if s. is_empty ( ) {
1362
- Self ( Inner :: Native ( None ) )
1377
+ Self :: new ( )
1363
1378
} else {
1364
1379
s. reserve_exact ( 1 ) ;
1365
1380
s. push ( '\0' ) ;
1366
1381
// No check for valid UTF-8 here
1367
- Self ( Inner :: Native ( Some ( s. into ( ) ) ) )
1382
+ Self ( Inner :: Native ( s. into ( ) ) )
1368
1383
}
1369
1384
}
1370
1385
}
@@ -1400,8 +1415,14 @@ impl From<&str> for GString {
1400
1415
if cfg ! ( debug_assertions) {
1401
1416
GStr :: check_interior_nuls ( s) . unwrap ( ) ;
1402
1417
}
1403
- if s. is_empty ( ) {
1404
- return Self :: default ( ) ;
1418
+ if s. len ( ) < INLINE_LEN {
1419
+ let mut data = <[ u8 ; INLINE_LEN ] >:: default ( ) ;
1420
+ let b = s. as_bytes ( ) ;
1421
+ unsafe { data. get_unchecked_mut ( ..b. len ( ) ) } . copy_from_slice ( b) ;
1422
+ return Self ( Inner :: Inline {
1423
+ len : b. len ( ) as u8 ,
1424
+ data,
1425
+ } ) ;
1405
1426
}
1406
1427
// Allocates with the GLib allocator
1407
1428
unsafe {
@@ -1420,7 +1441,7 @@ impl TryFrom<CString> for GString {
1420
1441
#[ inline]
1421
1442
fn try_from ( value : CString ) -> Result < Self , Self :: Error > {
1422
1443
if value. as_bytes ( ) . is_empty ( ) {
1423
- Ok ( Self ( Inner :: Native ( None ) ) )
1444
+ Ok ( Self :: new ( ) )
1424
1445
} else {
1425
1446
// Moves the content of the CString
1426
1447
// Also check if it's valid UTF-8
@@ -1431,7 +1452,7 @@ impl TryFrom<CString> for GString {
1431
1452
err,
1432
1453
)
1433
1454
} ) ?;
1434
- Ok ( Self ( Inner :: Native ( Some ( s. into ( ) ) ) ) )
1455
+ Ok ( Self ( Inner :: Native ( s. into ( ) ) ) )
1435
1456
}
1436
1457
}
1437
1458
}
@@ -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