/// Create a [`NSString`] from a static [`str`]. /// /// Equivalent to the [boxed C-strings] `@"string"` syntax in Objective-C. /// /// [`NSString`]: crate::NSString /// [boxed C-strings]: https://clang.llvm.org/docs/ObjectiveCLiterals.html#boxed-c-strings /// /// /// # Specification /// /// The macro takes any expression that evaluates to a `const` `&str`, and /// produces a `&'static NSString`. /// /// The returned string's encoding is not guaranteed to be UTF-8. /// /// Strings containing ASCII NUL is allowed, the NUL is preserved as one would /// expect. /// /// /// # Cargo features /// /// If the experimental `"unstable-static-nsstring"` feature is enabled, this /// will emit statics placed in special sections that will be replaced by dyld /// when the program starts up - which will in turn will cause the runtime /// cost of this macro to be completely non-existent! /// /// However, it is known to not be completely reliable yet, see [#258] for /// details. /// /// [#258]: https://github.com/madsmtm/objc2/issues/258 /// /// /// # Examples /// /// Creating a static `NSString`. /// /// ``` /// use objc2_foundation::{ns_string, NSString}; /// /// let hello: &'static NSString = ns_string!("hello"); /// assert_eq!(hello.to_string(), "hello"); /// ``` /// /// Creating a `NSString` from a `const` `&str`. /// /// ``` /// # use objc2_foundation::ns_string; /// const EXAMPLE: &str = "example"; /// assert_eq!(ns_string!(EXAMPLE).to_string(), EXAMPLE); /// ``` /// /// Creating unicode strings. /// /// ``` /// # use objc2_foundation::ns_string; /// let hello_ru = ns_string!("Привет"); /// assert_eq!(hello_ru.to_string(), "Привет"); /// ``` /// /// Creating a string containing a NUL byte: /// /// ``` /// # use objc2_foundation::ns_string; /// assert_eq!(ns_string!("example\0").to_string(), "example\0"); /// assert_eq!(ns_string!("exa\0mple").to_string(), "exa\0mple"); /// ``` // For auto_doc_cfg #[cfg(feature = "NSString")] #[macro_export] macro_rules! ns_string { ($s:expr) => {{ // Immediately place in constant for better UI const INPUT: &str = $s; $crate::__ns_string_inner!(INPUT) }}; } #[doc(hidden)] #[cfg(all(target_vendor = "apple", feature = "unstable-static-nsstring"))] #[macro_export] macro_rules! __ns_string_inner { ($inp:ident) => {{ const X: &[u8] = $inp.as_bytes(); $crate::__ns_string_static!(X); // Return &'static NSString CFSTRING.as_nsstring() }}; } #[doc(hidden)] #[cfg(all(target_vendor = "apple", feature = "unstable-static-nsstring"))] #[macro_export] macro_rules! __ns_string_static { ($inp:ident) => { // Note: We create both the ASCII + NUL and the UTF-16 + NUL versions // of the string, since we can't conditionally create a static. // // Since we don't add the `#[used]` attribute, Rust can fairly // reliably figure out that one of the variants are never used, and // exclude it. // Convert the input slice to a C-style string with a NUL byte. // // The section is the same as what clang sets, see: // https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CodeGenModule.cpp#L5192 #[link_section = "__TEXT,__cstring,cstring_literals"] static ASCII: [u8; $inp.len() + 1] = { // Zero-fill with $inp.len() + 1 let mut res: [u8; $inp.len() + 1] = [0; $inp.len() + 1]; let mut i = 0; // Fill with data from $inp while i < $inp.len() { res[i] = $inp[i]; i += 1; } // Now contains $inp + '\0' res }; // The full UTF-16 contents along with the written length. const UTF16_FULL: (&[u16; $inp.len()], usize) = { let mut out = [0u16; $inp.len()]; let mut iter = $crate::__ns_macro_helpers::EncodeUtf16Iter::new($inp); let mut written = 0; while let Some((state, chars)) = iter.next() { iter = state; out[written] = chars.repr[0]; written += 1; if chars.len > 1 { out[written] = chars.repr[1]; written += 1; } } (&{ out }, written) }; // Convert the slice to an UTF-16 array + a final NUL byte. // // The section is the same as what clang sets, see: // https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CodeGenModule.cpp#L5193 #[link_section = "__TEXT,__ustring"] static UTF16: [u16; UTF16_FULL.1 + 1] = { // Zero-fill with UTF16_FULL.1 + 1 let mut res: [u16; UTF16_FULL.1 + 1] = [0; UTF16_FULL.1 + 1]; let mut i = 0; // Fill with data from UTF16_FULL.0 up until UTF16_FULL.1 while i < UTF16_FULL.1 { res[i] = UTF16_FULL.0[i]; i += 1; } // Now contains UTF16_FULL.1 + NUL res }; // Create the constant string structure, and store it in a static // within a special section. // // The section is the same as what clang sets, see: // https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CodeGenModule.cpp#L5243 #[link_section = "__DATA,__cfstring"] static CFSTRING: $crate::__ns_macro_helpers::CFConstString = unsafe { if $crate::__ns_macro_helpers::is_ascii_no_nul($inp) { // This is technically an optimization (UTF-16 strings are // always valid), but it's a fairly important one! $crate::__ns_macro_helpers::CFConstString::new_ascii( &$crate::__ns_macro_helpers::__CFConstantStringClassReference, &ASCII, ) } else { $crate::__ns_macro_helpers::CFConstString::new_utf16( &$crate::__ns_macro_helpers::__CFConstantStringClassReference, &UTF16, ) } }; }; } #[doc(hidden)] #[cfg(not(all(target_vendor = "apple", feature = "unstable-static-nsstring")))] #[macro_export] macro_rules! __ns_string_inner { ($inp:ident) => {{ static CACHED_NSSTRING: $crate::__ns_macro_helpers::CachedRetained<$crate::NSString> = $crate::__ns_macro_helpers::CachedRetained::new(); CACHED_NSSTRING.get(|| $crate::NSString::from_str($inp)) }}; }