@@ -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 {
@@ -593,12 +598,17 @@ const INLINE_LEN: usize =
593
598
/// The constructors beginning with `from_utf8` `and `from_string` can also be used to further
594
599
/// control how interior nul-bytes are handled.
595
600
pub struct GString ( Inner ) ;
601
+
596
602
enum Inner {
597
- Native ( Option < Box < str > > ) ,
603
+ Native ( Box < str > ) ,
598
604
Foreign {
599
605
ptr : ptr:: NonNull < c_char > ,
600
606
len : usize ,
601
607
} ,
608
+ Inline {
609
+ len : u8 ,
610
+ data : [ u8 ; INLINE_LEN ] ,
611
+ } ,
602
612
}
603
613
604
614
unsafe impl Send for GString { }
@@ -611,7 +621,10 @@ impl GString {
611
621
/// Does not allocate.
612
622
#[ inline]
613
623
pub fn new ( ) -> Self {
614
- Self ( Inner :: Native ( None ) )
624
+ Self ( Inner :: Inline {
625
+ len : 0 ,
626
+ data : Default :: default ( ) ,
627
+ } )
615
628
}
616
629
// rustdoc-stripper-ignore-next
617
630
/// Formats an [`Arguments`](std::fmt::Arguments) into a [`GString`].
@@ -675,11 +688,11 @@ impl GString {
675
688
#[ inline]
676
689
pub unsafe fn from_utf8_unchecked ( mut v : Vec < u8 > ) -> Self {
677
690
if v. is_empty ( ) {
678
- Self ( Inner :: Native ( None ) )
691
+ Self :: new ( )
679
692
} else {
680
693
v. reserve_exact ( 1 ) ;
681
694
v. push ( 0 ) ;
682
- Self ( Inner :: Native ( Some ( String :: from_utf8_unchecked ( v) . into ( ) ) ) )
695
+ Self ( Inner :: Native ( String :: from_utf8_unchecked ( v) . into ( ) ) )
683
696
}
684
697
}
685
698
// rustdoc-stripper-ignore-next
@@ -695,9 +708,9 @@ impl GString {
695
708
return Err ( GStringNoTrailingNulError ( s. into_bytes ( ) ) . into ( ) ) ;
696
709
}
697
710
if s. len ( ) == 1 {
698
- Ok ( Self ( Inner :: Native ( None ) ) )
711
+ Ok ( Self :: new ( ) )
699
712
} else {
700
- Ok ( Self ( Inner :: Native ( Some ( s. into ( ) ) ) ) )
713
+ Ok ( Self ( Inner :: Native ( s. into ( ) ) ) )
701
714
}
702
715
}
703
716
// rustdoc-stripper-ignore-next
@@ -732,9 +745,9 @@ impl GString {
732
745
String :: from_utf8_unchecked ( v)
733
746
} ;
734
747
if s. len ( ) == 1 {
735
- Self ( Inner :: Native ( None ) )
748
+ Self :: new ( )
736
749
} else {
737
- Self ( Inner :: Native ( Some ( s. into ( ) ) ) )
750
+ Self ( Inner :: Native ( s. into ( ) ) )
738
751
}
739
752
}
740
753
// rustdoc-stripper-ignore-next
@@ -750,14 +763,14 @@ impl GString {
750
763
return Err ( GStringNoTrailingNulError ( bytes) . into ( ) ) ;
751
764
} ;
752
765
if nul_pos == 0 {
753
- Ok ( Self ( Inner :: Native ( None ) ) )
766
+ Ok ( Self :: new ( ) )
754
767
} else {
755
768
if let Err ( e) = std:: str:: from_utf8 ( unsafe { bytes. get_unchecked ( ..nul_pos) } ) {
756
769
return Err ( GStringUtf8Error ( bytes, e) . into ( ) ) ;
757
770
}
758
771
bytes. truncate ( nul_pos + 1 ) ;
759
772
let s = unsafe { String :: from_utf8_unchecked ( bytes) } ;
760
- Ok ( Self ( Inner :: Native ( Some ( s. into ( ) ) ) ) )
773
+ Ok ( Self ( Inner :: Native ( s. into ( ) ) ) )
761
774
}
762
775
}
763
776
// rustdoc-stripper-ignore-next
@@ -781,11 +794,11 @@ impl GString {
781
794
#[ inline]
782
795
pub fn from_string_unchecked ( mut s : String ) -> Self {
783
796
if s. is_empty ( ) {
784
- Self ( Inner :: Native ( None ) )
797
+ Self :: new ( )
785
798
} else {
786
799
s. reserve_exact ( 1 ) ;
787
800
s. push ( '\0' ) ;
788
- Self ( Inner :: Native ( Some ( s. into ( ) ) ) )
801
+ Self ( Inner :: Native ( s. into ( ) ) )
789
802
}
790
803
}
791
804
// rustdoc-stripper-ignore-next
@@ -794,9 +807,9 @@ impl GString {
794
807
pub fn as_str ( & self ) -> & str {
795
808
unsafe {
796
809
let ( ptr, len) = match self . 0 {
797
- Inner :: Native ( None ) => ( ptr:: null ( ) , 0 ) ,
798
- Inner :: Native ( Some ( ref s) ) => ( s. as_ptr ( ) as * const u8 , s. len ( ) - 1 ) ,
810
+ Inner :: Native ( ref s) => ( s. as_ptr ( ) as * const u8 , s. len ( ) - 1 ) ,
799
811
Inner :: Foreign { ptr, len } => ( ptr. as_ptr ( ) as * const u8 , len) ,
812
+ Inner :: Inline { len, ref data } => ( data. as_ptr ( ) , len as usize ) ,
800
813
} ;
801
814
if len == 0 {
802
815
""
@@ -812,12 +825,12 @@ impl GString {
812
825
#[ inline]
813
826
pub fn as_gstr ( & self ) -> & GStr {
814
827
let bytes = match self . 0 {
815
- Inner :: Native ( None ) => return <& GStr >:: default ( ) ,
816
- Inner :: Native ( Some ( ref s) ) => s. as_bytes ( ) ,
828
+ Inner :: Native ( ref s) => s. as_bytes ( ) ,
817
829
Inner :: Foreign { len, .. } if len == 0 => & [ 0 ] ,
818
830
Inner :: Foreign { ptr, len } => unsafe {
819
831
slice:: from_raw_parts ( ptr. as_ptr ( ) as * const _ , len + 1 )
820
832
} ,
833
+ Inner :: Inline { len, ref data } => unsafe { data. get_unchecked ( ..len as usize + 1 ) } ,
821
834
} ;
822
835
unsafe { GStr :: from_utf8_with_nul_unchecked ( bytes) }
823
836
}
@@ -827,9 +840,9 @@ impl GString {
827
840
#[ inline]
828
841
pub fn as_ptr ( & self ) -> * const c_char {
829
842
match self . 0 {
830
- Inner :: Native ( None ) => <& GStr >:: default ( ) . as_ptr ( ) ,
831
- Inner :: Native ( Some ( ref s) ) => s. as_ptr ( ) as * const _ ,
843
+ Inner :: Native ( ref s) => s. as_ptr ( ) as * const _ ,
832
844
Inner :: Foreign { ptr, .. } => ptr. as_ptr ( ) ,
845
+ Inner :: Inline { ref data, .. } => data. as_ptr ( ) as * const _ ,
833
846
}
834
847
}
835
848
@@ -839,34 +852,34 @@ impl GString {
839
852
/// The returned buffer is not guaranteed to contain a trailing nul-byte.
840
853
pub fn into_bytes ( mut self ) -> Vec < u8 > {
841
854
match & mut self . 0 {
842
- Inner :: Native ( s) => match s. take ( ) {
843
- None => Vec :: new ( ) ,
844
- Some ( s) => {
845
- let mut s = String :: from ( s) ;
846
- let _nul = s. pop ( ) ;
847
- debug_assert_eq ! ( _nul, Some ( '\0' ) ) ;
848
- s. into_bytes ( )
849
- }
850
- } ,
855
+ Inner :: Native ( s) => {
856
+ let mut s = String :: from ( mem:: replace ( s, "" . into ( ) ) ) ;
857
+ let _nul = s. pop ( ) ;
858
+ debug_assert_eq ! ( _nul, Some ( '\0' ) ) ;
859
+ s. into_bytes ( )
860
+ }
851
861
Inner :: Foreign { ptr, len } => {
852
862
let bytes = unsafe { slice:: from_raw_parts ( ptr. as_ptr ( ) as * const u8 , * len - 1 ) } ;
853
863
bytes. to_owned ( )
854
864
}
865
+ Inner :: Inline { len, data } => {
866
+ unsafe { data. get_unchecked ( ..* len as usize ) } . to_owned ( )
867
+ }
855
868
}
856
869
}
857
870
858
871
// rustdoc-stripper-ignore-next
859
872
/// Consumes the `GString` and returns the underlying byte buffer, with trailing nul-byte.
860
873
pub fn into_bytes_with_nul ( mut self ) -> Vec < u8 > {
861
874
match & mut self . 0 {
862
- Inner :: Native ( s) => match s. take ( ) {
863
- None => vec ! [ 0u8 ] ,
864
- Some ( s) => str:: into_boxed_bytes ( s) . into ( ) ,
865
- } ,
875
+ Inner :: Native ( s) => str:: into_boxed_bytes ( mem:: replace ( s, "" . into ( ) ) ) . into ( ) ,
866
876
Inner :: Foreign { ptr, len } => {
867
877
let bytes = unsafe { slice:: from_raw_parts ( ptr. as_ptr ( ) as * const u8 , * len) } ;
868
878
bytes. to_owned ( )
869
879
}
880
+ Inner :: Inline { len, data } => {
881
+ unsafe { data. get_unchecked ( ..* len as usize + 1 ) } . to_owned ( )
882
+ }
870
883
}
871
884
}
872
885
}
@@ -998,12 +1011,14 @@ impl IntoGlibPtr<*mut c_char> for GString {
998
1011
/// Transform into a nul-terminated raw C string pointer.
999
1012
unsafe fn into_glib_ptr ( self ) -> * mut c_char {
1000
1013
match self . 0 {
1001
- Inner :: Native ( None ) => ffi:: g_malloc0 ( 1 ) as * mut _ ,
1002
- Inner :: Native ( Some ( ref s) ) => ffi:: g_strndup ( s. as_ptr ( ) as * const _ , s. len ( ) ) ,
1014
+ Inner :: Native ( ref s) => ffi:: g_strndup ( s. as_ptr ( ) as * const _ , s. len ( ) ) ,
1003
1015
Inner :: Foreign { ptr, .. } => {
1004
1016
let _s = mem:: ManuallyDrop :: new ( self ) ;
1005
1017
ptr. as_ptr ( )
1006
1018
}
1019
+ Inner :: Inline { len, ref data } => {
1020
+ ffi:: g_strndup ( data. as_ptr ( ) as * const _ , len as usize )
1021
+ }
1007
1022
}
1008
1023
}
1009
1024
}
@@ -1251,22 +1266,22 @@ impl From<GString> for String {
1251
1266
#[ inline]
1252
1267
fn from ( mut s : GString ) -> Self {
1253
1268
match & mut s. 0 {
1254
- Inner :: Native ( s) => match s. take ( ) {
1255
- None => Self :: default ( ) ,
1256
- Some ( s) => {
1257
- // Moves the underlying string
1258
- let mut s = String :: from ( s) ;
1259
- let _nul = s. pop ( ) ;
1260
- debug_assert_eq ! ( _nul, Some ( '\0' ) ) ;
1261
- s
1262
- }
1263
- } ,
1269
+ Inner :: Native ( s) => {
1270
+ // Moves the underlying string
1271
+ let mut s = String :: from ( mem:: replace ( s, "" . into ( ) ) ) ;
1272
+ let _nul = s. pop ( ) ;
1273
+ debug_assert_eq ! ( _nul, Some ( '\0' ) ) ;
1274
+ s
1275
+ }
1264
1276
Inner :: Foreign { len, .. } if * len == 0 => String :: new ( ) ,
1265
1277
Inner :: Foreign { ptr, len } => unsafe {
1266
1278
// Creates a copy
1267
1279
let slice = slice:: from_raw_parts ( ptr. as_ptr ( ) as * const u8 , * len) ;
1268
1280
std:: str:: from_utf8_unchecked ( slice) . into ( )
1269
1281
} ,
1282
+ Inner :: Inline { len, data } => unsafe {
1283
+ std:: str:: from_utf8_unchecked ( data. get_unchecked ( ..* len as usize ) ) . to_owned ( )
1284
+ } ,
1270
1285
}
1271
1286
}
1272
1287
}
@@ -1323,12 +1338,12 @@ impl From<String> for GString {
1323
1338
GStr :: check_interior_nuls ( & s) . unwrap ( ) ;
1324
1339
}
1325
1340
if s. is_empty ( ) {
1326
- Self ( Inner :: Native ( None ) )
1341
+ Self :: new ( )
1327
1342
} else {
1328
1343
s. reserve_exact ( 1 ) ;
1329
1344
s. push ( '\0' ) ;
1330
1345
// No check for valid UTF-8 here
1331
- Self ( Inner :: Native ( Some ( s. into ( ) ) ) )
1346
+ Self ( Inner :: Native ( s. into ( ) ) )
1332
1347
}
1333
1348
}
1334
1349
}
@@ -1364,8 +1379,14 @@ impl From<&str> for GString {
1364
1379
if cfg ! ( debug_assertions) {
1365
1380
GStr :: check_interior_nuls ( s) . unwrap ( ) ;
1366
1381
}
1367
- if s. is_empty ( ) {
1368
- return Self :: default ( ) ;
1382
+ if s. len ( ) < INLINE_LEN {
1383
+ let mut data = <[ u8 ; INLINE_LEN ] >:: default ( ) ;
1384
+ let b = s. as_bytes ( ) ;
1385
+ unsafe { data. get_unchecked_mut ( ..b. len ( ) ) } . copy_from_slice ( b) ;
1386
+ return Self ( Inner :: Inline {
1387
+ len : b. len ( ) as u8 ,
1388
+ data,
1389
+ } ) ;
1369
1390
}
1370
1391
// Allocates with the GLib allocator
1371
1392
unsafe {
@@ -1384,7 +1405,7 @@ impl TryFrom<CString> for GString {
1384
1405
#[ inline]
1385
1406
fn try_from ( value : CString ) -> Result < Self , Self :: Error > {
1386
1407
if value. as_bytes ( ) . is_empty ( ) {
1387
- Ok ( Self ( Inner :: Native ( None ) ) )
1408
+ Ok ( Self :: new ( ) )
1388
1409
} else {
1389
1410
// Moves the content of the CString
1390
1411
// Also check if it's valid UTF-8
@@ -1395,7 +1416,7 @@ impl TryFrom<CString> for GString {
1395
1416
err,
1396
1417
)
1397
1418
} ) ?;
1398
- Ok ( Self ( Inner :: Native ( Some ( s. into ( ) ) ) ) )
1419
+ Ok ( Self ( Inner :: Native ( s. into ( ) ) ) )
1399
1420
}
1400
1421
}
1401
1422
}
@@ -2002,4 +2023,15 @@ mod tests {
2002
2023
let s = gformat ! ( "bla bla {} bla" , 123 ) ;
2003
2024
assert_eq ! ( s, "bla bla 123 bla" ) ;
2004
2025
}
2026
+
2027
+ #[ test]
2028
+ fn layout ( ) {
2029
+ // ensure the inline variant is not wider than the other variants
2030
+ enum NoInline {
2031
+ _Native( Box < str > ) ,
2032
+ _Foreign( ptr:: NonNull < c_char > , usize ) ,
2033
+ }
2034
+ assert_eq ! ( mem:: size_of:: <GString >( ) , mem:: size_of:: <NoInline >( ) ) ;
2035
+ assert_eq ! ( mem:: size_of:: <GString >( ) , mem:: size_of:: <String >( ) ) ;
2036
+ }
2005
2037
}
0 commit comments