9
9
//! generating a pure `NSString`. We don't support that yet (since I don't
10
10
//! know the use-case), but we definitely could!
11
11
//! See: <https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CGObjCMac.cpp#L2007-L2068>
12
- #![ cfg( feature = "apple" ) ]
13
12
use core:: ffi:: c_void;
13
+ use core:: mem:: ManuallyDrop ;
14
+ use core:: ptr;
15
+ use core:: sync:: atomic:: { AtomicPtr , Ordering } ;
14
16
17
+ use objc2:: rc:: Id ;
15
18
use objc2:: runtime:: Class ;
16
19
17
20
use crate :: NSString ;
@@ -89,10 +92,17 @@ impl CFConstString {
89
92
}
90
93
91
94
#[ inline]
92
- pub const fn as_nsstring ( & self ) -> & NSString {
95
+ pub const fn as_nsstring_const ( & self ) -> & NSString {
93
96
let ptr: * const Self = self ;
94
97
unsafe { & * ptr. cast :: < NSString > ( ) }
95
98
}
99
+
100
+ // This is deliberately not `const` to prevent the result from being used
101
+ // in other statics, since not all platforms support that (yet).
102
+ #[ inline]
103
+ pub fn as_nsstring ( & self ) -> & NSString {
104
+ self . as_nsstring_const ( )
105
+ }
96
106
}
97
107
98
108
/// Returns `true` if `bytes` is entirely ASCII with no interior NULs.
@@ -193,6 +203,40 @@ const fn decode_utf8(s: &[u8], i: usize) -> (usize, u32) {
193
203
}
194
204
}
195
205
206
+ /// Allows storing a [`NSString`] in a static and lazily loading it.
207
+ #[ doc( hidden) ]
208
+ pub struct CachedNSString {
209
+ ptr : AtomicPtr < NSString > ,
210
+ }
211
+
212
+ impl CachedNSString {
213
+ /// Constructs a new [`CachedNSString`].
214
+ pub const fn new ( ) -> Self {
215
+ Self {
216
+ ptr : AtomicPtr :: new ( ptr:: null_mut ( ) ) ,
217
+ }
218
+ }
219
+
220
+ /// Returns the cached NSString. If no string is yet cached, creates one
221
+ /// with the given name and stores it.
222
+ #[ inline]
223
+ pub fn get ( & self , s : & str ) -> & ' static NSString {
224
+ // TODO: Investigate if we can use weaker orderings.
225
+ let ptr = self . ptr . load ( Ordering :: SeqCst ) ;
226
+ // SAFETY: The pointer is either NULL, or has been created below.
227
+ unsafe { ptr. as_ref ( ) } . unwrap_or_else ( || {
228
+ // "Forget" about releasing the string, effectively promoting it
229
+ // to a static.
230
+ let s = ManuallyDrop :: new ( NSString :: from_str ( s) ) ;
231
+ let ptr = Id :: as_ptr ( & s) ;
232
+ self . ptr . store ( ptr as * mut NSString , Ordering :: SeqCst ) ;
233
+ // SAFETY: The pointer is valid, and will always be valid, since
234
+ // we haven't released it.
235
+ unsafe { ptr. as_ref ( ) . unwrap_unchecked ( ) }
236
+ } )
237
+ }
238
+ }
239
+
196
240
/// Creates an [`NSString`][`crate::NSString`] from a static string.
197
241
///
198
242
/// Currently only supported on Apple targets.
@@ -201,30 +245,20 @@ const fn decode_utf8(s: &[u8], i: usize) -> (usize, u32) {
201
245
/// # Examples
202
246
///
203
247
/// This macro takes a either a `"string"` literal or `const` string slice as
204
- /// the argument:
248
+ /// the argument, and produces a `&'static NSString` :
205
249
///
206
250
/// ```
207
- /// use objc2_foundation::ns_string;
208
- /// let hello = ns_string!("hello");
251
+ /// use objc2_foundation::{ns_string, NSString};
252
+ /// # #[cfg(feature = "gnustep-1-7")]
253
+ /// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
254
+ /// let hello: &'static NSString = ns_string!("hello");
209
255
/// assert_eq!(hello.to_string(), "hello");
210
256
///
211
257
/// const WORLD: &str = "world";
212
258
/// let world = ns_string!(WORLD);
213
259
/// assert_eq!(world.to_string(), WORLD);
214
260
/// ```
215
261
///
216
- /// The result of this macro can even be used to create `static` values:
217
- ///
218
- /// ```
219
- /// # use objc2_foundation::{ns_string, NSString};
220
- /// static WORLD: &NSString = ns_string!("world");
221
- ///
222
- /// assert_eq!(WORLD.to_string(), "world");
223
- /// ```
224
- ///
225
- /// Note that the result cannot be used in a `const` because it refers to
226
- /// static data outside of this library.
227
- ///
228
262
///
229
263
/// # Unicode Strings
230
264
///
@@ -233,9 +267,11 @@ const fn decode_utf8(s: &[u8], i: usize) -> (usize, u32) {
233
267
/// string to the most efficient encoding, you don't have to do anything!
234
268
///
235
269
/// ```
236
- /// # use objc2_foundation::{ns_string, NSString};
237
- /// static HELLO_RU: &NSString = ns_string!("Привет");
238
- /// assert_eq!(HELLO_RU.to_string(), "Привет");
270
+ /// # use objc2_foundation::ns_string;
271
+ /// # #[cfg(feature = "gnustep-1-7")]
272
+ /// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
273
+ /// let hello_ru = ns_string!("Привет");
274
+ /// assert_eq!(hello_ru.to_string(), "Привет");
239
275
/// ```
240
276
///
241
277
/// Note that because this is implemented with `const` evaluation, massive
@@ -250,6 +286,8 @@ const fn decode_utf8(s: &[u8], i: usize) -> (usize, u32) {
250
286
///
251
287
/// ```
252
288
/// # use objc2_foundation::ns_string;
289
+ /// # #[cfg(feature = "gnustep-1-7")]
290
+ /// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
253
291
/// let example = ns_string!("example\0");
254
292
/// assert_eq!(example.to_string(), "example\0");
255
293
///
@@ -269,40 +307,54 @@ const fn decode_utf8(s: &[u8], i: usize) -> (usize, u32) {
269
307
///
270
308
/// [`NSString::from_str`]: crate::NSString::from_str
271
309
#[ macro_export]
272
- #[ cfg( feature = "apple" ) ] // To make `auto_doc_cfg` pick this up
273
310
macro_rules! ns_string {
274
311
( $s: expr) => { {
312
+ // Immediately place in constant for better UI
313
+ const INPUT : & str = $s;
314
+ $crate:: __ns_string_inner!( INPUT )
315
+ } } ;
316
+ }
317
+
318
+ #[ doc( hidden) ]
319
+ #[ cfg( feature = "apple" ) ]
320
+ #[ macro_export]
321
+ macro_rules! __ns_string_inner {
322
+ ( $inp: ident) => { {
323
+ const X : & [ u8 ] = $inp. as_bytes( ) ;
324
+ $crate:: __ns_string_inner!( @inner X ) ;
325
+ // Return &'static NSString
326
+ CFSTRING . as_nsstring( )
327
+ } } ;
328
+ ( @inner $inp: ident) => {
275
329
// Note: We create both the ASCII + NUL and the UTF-16 + NUL versions
276
330
// of the string, since we can't conditionally create a static.
277
331
//
278
332
// Since we don't add the `#[used]` attribute, Rust can fairly easily
279
333
// figure out that one of the variants are never used, and simply
280
334
// exclude it.
281
335
282
- const INPUT : & [ u8 ] = $s. as_bytes( ) ;
283
-
284
336
// Convert the input slice to a C-style string with a NUL byte.
285
337
//
286
338
// The section is the same as what clang sets, see:
287
339
// https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CodeGenModule.cpp#L5192
288
340
#[ link_section = "__TEXT,__cstring,cstring_literals" ]
289
- static ASCII : [ u8 ; INPUT . len( ) + 1 ] = {
290
- // Zero-fill with INPUT .len() + 1
291
- let mut res: [ u8 ; INPUT . len( ) + 1 ] = [ 0 ; INPUT . len( ) + 1 ] ;
341
+ static ASCII : [ u8 ; $inp . len( ) + 1 ] = {
342
+ // Zero-fill with $inp .len() + 1
343
+ let mut res: [ u8 ; $inp . len( ) + 1 ] = [ 0 ; $inp . len( ) + 1 ] ;
292
344
let mut i = 0 ;
293
- // Fill with data from INPUT
294
- while i < INPUT . len( ) {
295
- res[ i] = INPUT [ i] ;
345
+ // Fill with data from $inp
346
+ while i < $inp . len( ) {
347
+ res[ i] = $inp [ i] ;
296
348
i += 1 ;
297
349
}
298
- // Now contains INPUT + '\0'
350
+ // Now contains $inp + '\0'
299
351
res
300
352
} ;
301
353
302
354
// The full UTF-16 contents along with the written length.
303
- const UTF16_FULL : ( & [ u16 ; INPUT . len( ) ] , usize ) = {
304
- let mut out = [ 0u16 ; INPUT . len( ) ] ;
305
- let mut iter = $crate:: __string_macro:: EncodeUtf16Iter :: new( INPUT ) ;
355
+ const UTF16_FULL : ( & [ u16 ; $inp . len( ) ] , usize ) = {
356
+ let mut out = [ 0u16 ; $inp . len( ) ] ;
357
+ let mut iter = $crate:: __string_macro:: EncodeUtf16Iter :: new( $inp ) ;
306
358
let mut written = 0 ;
307
359
308
360
while let Some ( ( state, chars) ) = iter. next( ) {
@@ -344,7 +396,7 @@ macro_rules! ns_string {
344
396
// https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CodeGenModule.cpp#L5243
345
397
#[ link_section = "__DATA,__cfstring" ]
346
398
static CFSTRING : $crate:: __string_macro:: CFConstString = unsafe {
347
- if $crate:: __string_macro:: is_ascii_no_nul( INPUT ) {
399
+ if $crate:: __string_macro:: is_ascii_no_nul( $inp ) {
348
400
// This is technically an optimization (UTF-16 strings are
349
401
// always valid), but it's a fairly important one!
350
402
$crate:: __string_macro:: CFConstString :: new_ascii(
@@ -358,9 +410,17 @@ macro_rules! ns_string {
358
410
)
359
411
}
360
412
} ;
413
+ } ;
414
+ }
361
415
362
- // Return &'static NSString
363
- CFSTRING . as_nsstring( )
416
+ #[ doc( hidden) ]
417
+ #[ cfg( not( feature = "apple" ) ) ]
418
+ #[ macro_export]
419
+ macro_rules! __ns_string_inner {
420
+ ( $inp: ident) => { {
421
+ use $crate:: __string_macro:: CachedNSString ;
422
+ static CACHED_NSSTRING : CachedNSString = CachedNSString :: new( ) ;
423
+ CACHED_NSSTRING . get( $inp)
364
424
} } ;
365
425
}
366
426
@@ -422,14 +482,14 @@ mod tests {
422
482
fn ns_string ( ) {
423
483
macro_rules! test {
424
484
( $( $s: expr, ) +) => { $( {
425
- static STRING : & NSString = ns_string!( $s) ;
426
- let s = NSString :: from_str( $s) ;
485
+ let s1 = ns_string!( $s) ;
486
+ let s2 = NSString :: from_str( $s) ;
427
487
428
- assert_eq!( STRING , STRING ) ;
429
- assert_eq!( STRING , & * s ) ;
488
+ assert_eq!( s1 , s1 ) ;
489
+ assert_eq!( s1 , & * s2 ) ;
430
490
431
- assert_eq!( STRING . to_string( ) , $s) ;
432
- assert_eq!( s . to_string( ) , $s) ;
491
+ assert_eq!( s1 . to_string( ) , $s) ;
492
+ assert_eq!( s2 . to_string( ) , $s) ;
433
493
} ) +} ;
434
494
}
435
495
0 commit comments