99//! generating a pure `NSString`. We don't support that yet (since I don't
1010//! know the use-case), but we definitely could!
1111//! See: <https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CGObjCMac.cpp#L2007-L2068>
12- #![ cfg( feature = "apple" ) ]
1312use core:: ffi:: c_void;
13+ use core:: mem:: ManuallyDrop ;
14+ use core:: ptr;
15+ use core:: sync:: atomic:: { AtomicPtr , Ordering } ;
1416
17+ use objc2:: rc:: Id ;
1518use objc2:: runtime:: Class ;
1619
1720use crate :: NSString ;
@@ -89,10 +92,17 @@ impl CFConstString {
8992 }
9093
9194 #[ inline]
92- pub const fn as_nsstring ( & self ) -> & NSString {
95+ pub const fn as_nsstring_const ( & self ) -> & NSString {
9396 let ptr: * const Self = self ;
9497 unsafe { & * ptr. cast :: < NSString > ( ) }
9598 }
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+ }
96106}
97107
98108/// 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) {
193203 }
194204}
195205
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+
196240/// Creates an [`NSString`][`crate::NSString`] from a static string.
197241///
198242/// Currently only supported on Apple targets.
@@ -201,30 +245,20 @@ const fn decode_utf8(s: &[u8], i: usize) -> (usize, u32) {
201245/// # Examples
202246///
203247/// This macro takes a either a `"string"` literal or `const` string slice as
204- /// the argument:
248+ /// the argument, and produces a `&'static NSString` :
205249///
206250/// ```
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");
209255/// assert_eq!(hello.to_string(), "hello");
210256///
211257/// const WORLD: &str = "world";
212258/// let world = ns_string!(WORLD);
213259/// assert_eq!(world.to_string(), WORLD);
214260/// ```
215261///
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- ///
228262///
229263/// # Unicode Strings
230264///
@@ -233,9 +267,11 @@ const fn decode_utf8(s: &[u8], i: usize) -> (usize, u32) {
233267/// string to the most efficient encoding, you don't have to do anything!
234268///
235269/// ```
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(), "Привет");
239275/// ```
240276///
241277/// Note that because this is implemented with `const` evaluation, massive
@@ -250,6 +286,8 @@ const fn decode_utf8(s: &[u8], i: usize) -> (usize, u32) {
250286///
251287/// ```
252288/// # use objc2_foundation::ns_string;
289+ /// # #[cfg(feature = "gnustep-1-7")]
290+ /// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
253291/// let example = ns_string!("example\0");
254292/// assert_eq!(example.to_string(), "example\0");
255293///
@@ -269,40 +307,54 @@ const fn decode_utf8(s: &[u8], i: usize) -> (usize, u32) {
269307///
270308/// [`NSString::from_str`]: crate::NSString::from_str
271309#[ macro_export]
272- #[ cfg( feature = "apple" ) ] // To make `auto_doc_cfg` pick this up
273310macro_rules! ns_string {
274311 ( $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) => {
275329 // Note: We create both the ASCII + NUL and the UTF-16 + NUL versions
276330 // of the string, since we can't conditionally create a static.
277331 //
278332 // Since we don't add the `#[used]` attribute, Rust can fairly easily
279333 // figure out that one of the variants are never used, and simply
280334 // exclude it.
281335
282- const INPUT : & [ u8 ] = $s. as_bytes( ) ;
283-
284336 // Convert the input slice to a C-style string with a NUL byte.
285337 //
286338 // The section is the same as what clang sets, see:
287339 // https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CodeGenModule.cpp#L5192
288340 #[ 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 ] ;
292344 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] ;
296348 i += 1 ;
297349 }
298- // Now contains INPUT + '\0'
350+ // Now contains $inp + '\0'
299351 res
300352 } ;
301353
302354 // 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 ) ;
306358 let mut written = 0 ;
307359
308360 while let Some ( ( state, chars) ) = iter. next( ) {
@@ -344,7 +396,7 @@ macro_rules! ns_string {
344396 // https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CodeGenModule.cpp#L5243
345397 #[ link_section = "__DATA,__cfstring" ]
346398 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 ) {
348400 // This is technically an optimization (UTF-16 strings are
349401 // always valid), but it's a fairly important one!
350402 $crate:: __string_macro:: CFConstString :: new_ascii(
@@ -358,9 +410,17 @@ macro_rules! ns_string {
358410 )
359411 }
360412 } ;
413+ } ;
414+ }
361415
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)
364424 } } ;
365425}
366426
@@ -422,14 +482,14 @@ mod tests {
422482 fn ns_string ( ) {
423483 macro_rules! test {
424484 ( $( $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) ;
427487
428- assert_eq!( STRING , STRING ) ;
429- assert_eq!( STRING , & * s ) ;
488+ assert_eq!( s1 , s1 ) ;
489+ assert_eq!( s1 , & * s2 ) ;
430490
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) ;
433493 } ) +} ;
434494 }
435495
0 commit comments