objc2_foundation/macros/
ns_string.rs

1/// Create a [`NSString`] from a static [`str`].
2///
3/// Equivalent to the [boxed C-strings] `@"string"` syntax in Objective-C.
4///
5/// [`NSString`]: crate::NSString
6/// [boxed C-strings]: https://clang.llvm.org/docs/ObjectiveCLiterals.html#boxed-c-strings
7///
8///
9/// # Specification
10///
11/// The macro takes any expression that evaluates to a `const` `&str`, and
12/// produces a `&'static NSString`.
13///
14/// The returned string's encoding is not guaranteed to be UTF-8.
15///
16/// Strings containing ASCII NUL is allowed, the NUL is preserved as one would
17/// expect.
18///
19///
20/// # Cargo features
21///
22/// If the experimental `"unstable-static-nsstring"` feature is enabled, this
23/// will emit statics placed in special sections that will be replaced by dyld
24/// when the program starts up - which will in turn will cause the runtime
25/// cost of this macro to be completely non-existent!
26///
27/// However, it is known to not be completely reliable yet, see [#258] for
28/// details.
29///
30/// [#258]: https://github.com/madsmtm/objc2/issues/258
31///
32///
33/// # Examples
34///
35/// Creating a static `NSString`.
36///
37/// ```
38/// use objc2_foundation::{ns_string, NSString};
39///
40/// let hello: &'static NSString = ns_string!("hello");
41/// assert_eq!(hello.to_string(), "hello");
42/// ```
43///
44/// Creating a `NSString` from a `const` `&str`.
45///
46/// ```
47/// # use objc2_foundation::ns_string;
48/// const EXAMPLE: &str = "example";
49/// assert_eq!(ns_string!(EXAMPLE).to_string(), EXAMPLE);
50/// ```
51///
52/// Creating unicode strings.
53///
54/// ```
55/// # use objc2_foundation::ns_string;
56/// let hello_ru = ns_string!("Привет");
57/// assert_eq!(hello_ru.to_string(), "Привет");
58/// ```
59///
60/// Creating a string containing a NUL byte:
61///
62/// ```
63/// # use objc2_foundation::ns_string;
64/// assert_eq!(ns_string!("example\0").to_string(), "example\0");
65/// assert_eq!(ns_string!("exa\0mple").to_string(), "exa\0mple");
66/// ```
67// For auto_doc_cfg
68#[cfg(feature = "NSString")]
69#[macro_export]
70macro_rules! ns_string {
71    ($s:expr) => {{
72        // Immediately place in constant for better UI
73        const INPUT: &str = $s;
74        $crate::__ns_string_inner!(INPUT)
75    }};
76}
77
78#[doc(hidden)]
79#[cfg(all(target_vendor = "apple", feature = "unstable-static-nsstring"))]
80#[macro_export]
81macro_rules! __ns_string_inner {
82    ($inp:ident) => {{
83        const X: &[u8] = $inp.as_bytes();
84        $crate::__ns_string_static!(X);
85        // Return &'static NSString
86        CFSTRING.as_nsstring()
87    }};
88}
89
90#[doc(hidden)]
91#[cfg(all(target_vendor = "apple", feature = "unstable-static-nsstring"))]
92#[macro_export]
93macro_rules! __ns_string_static {
94    ($inp:ident) => {
95        // Note: We create both the ASCII + NUL and the UTF-16 + NUL versions
96        // of the string, since we can't conditionally create a static.
97        //
98        // Since we don't add the `#[used]` attribute, Rust can fairly
99        // reliably figure out that one of the variants are never used, and
100        // exclude it.
101
102        // Convert the input slice to a C-style string with a NUL byte.
103        //
104        // The section is the same as what clang sets, see:
105        // https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CodeGenModule.cpp#L5192
106        #[link_section = "__TEXT,__cstring,cstring_literals"]
107        static ASCII: [u8; $inp.len() + 1] = {
108            // Zero-fill with $inp.len() + 1
109            let mut res: [u8; $inp.len() + 1] = [0; $inp.len() + 1];
110            let mut i = 0;
111            // Fill with data from $inp
112            while i < $inp.len() {
113                res[i] = $inp[i];
114                i += 1;
115            }
116            // Now contains $inp + '\0'
117            res
118        };
119
120        // The full UTF-16 contents along with the written length.
121        const UTF16_FULL: (&[u16; $inp.len()], usize) = {
122            let mut out = [0u16; $inp.len()];
123            let mut iter = $crate::__ns_macro_helpers::EncodeUtf16Iter::new($inp);
124            let mut written = 0;
125
126            while let Some((state, chars)) = iter.next() {
127                iter = state;
128                out[written] = chars.repr[0];
129                written += 1;
130
131                if chars.len > 1 {
132                    out[written] = chars.repr[1];
133                    written += 1;
134                }
135            }
136
137            (&{ out }, written)
138        };
139
140        // Convert the slice to an UTF-16 array + a final NUL byte.
141        //
142        // The section is the same as what clang sets, see:
143        // https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CodeGenModule.cpp#L5193
144        #[link_section = "__TEXT,__ustring"]
145        static UTF16: [u16; UTF16_FULL.1 + 1] = {
146            // Zero-fill with UTF16_FULL.1 + 1
147            let mut res: [u16; UTF16_FULL.1 + 1] = [0; UTF16_FULL.1 + 1];
148            let mut i = 0;
149            // Fill with data from UTF16_FULL.0 up until UTF16_FULL.1
150            while i < UTF16_FULL.1 {
151                res[i] = UTF16_FULL.0[i];
152                i += 1;
153            }
154            // Now contains UTF16_FULL.1 + NUL
155            res
156        };
157
158        // Create the constant string structure, and store it in a static
159        // within a special section.
160        //
161        // The section is the same as what clang sets, see:
162        // https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CodeGenModule.cpp#L5243
163        #[link_section = "__DATA,__cfstring"]
164        static CFSTRING: $crate::__ns_macro_helpers::CFConstString = unsafe {
165            if $crate::__ns_macro_helpers::is_ascii_no_nul($inp) {
166                // This is technically an optimization (UTF-16 strings are
167                // always valid), but it's a fairly important one!
168                $crate::__ns_macro_helpers::CFConstString::new_ascii(
169                    &$crate::__ns_macro_helpers::__CFConstantStringClassReference,
170                    &ASCII,
171                )
172            } else {
173                $crate::__ns_macro_helpers::CFConstString::new_utf16(
174                    &$crate::__ns_macro_helpers::__CFConstantStringClassReference,
175                    &UTF16,
176                )
177            }
178        };
179    };
180}
181
182#[doc(hidden)]
183#[cfg(not(all(target_vendor = "apple", feature = "unstable-static-nsstring")))]
184#[macro_export]
185macro_rules! __ns_string_inner {
186    ($inp:ident) => {{
187        static CACHED_NSSTRING: $crate::__ns_macro_helpers::CachedRetained<$crate::NSString> =
188            $crate::__ns_macro_helpers::CachedRetained::new();
189        CACHED_NSSTRING.get(|| $crate::NSString::from_str($inp))
190    }};
191}