Skip to content

Commit ef95554

Browse files
committed
Put static functionality in ns_string! behind an unstable feature flag
1 parent 8686688 commit ef95554

File tree

12 files changed

+134
-195
lines changed

12 files changed

+134
-195
lines changed

.github/workflows/ci.yml

+3-2
Original file line numberDiff line numberDiff line change
@@ -481,12 +481,13 @@ jobs:
481481
cargo test $ARGS $PUBLIC_CRATES -ptests
482482
--features=$INTERESTING_FEATURES,catch-all,Foundation_all,$UNSTABLE_FEATURES
483483
484-
# TODO: Re-enable this on Foundation once we do some form of
484+
# TODO: Re-enable this on all of Foundation once we do some form of
485485
# availability checking.
486486
- name: Test static class and selectors
487487
run: >-
488488
cargo test $ARGS $PUBLIC_CRATES -ptests
489-
--features=unstable-static-sel,unstable-static-class
489+
--features=unstable-static-sel,unstable-static-class,unstable-static-nsstring
490+
--features=Foundation,Foundation_NSString
490491
491492
test-ios:
492493
# if: ${{ env.FULL }}

crates/icrate/Cargo.toml

+6
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,12 @@ unstable-docsrs = []
110110
# exist.
111111
unstable-private = []
112112

113+
# Make the `ns_string!` macro create the string statically.
114+
#
115+
# Please test it, and report any issues you may find:
116+
# https://github.com/madsmtm/objc2/issues/new
117+
unstable-static-nsstring = []
118+
113119
# Frameworks
114120

115121
AppKit = [
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use core::mem::ManuallyDrop;
2+
use core::ptr;
3+
use core::sync::atomic::{AtomicPtr, Ordering};
4+
5+
use objc2::rc::{Id, Shared};
6+
use objc2::Message;
7+
8+
/// Allows storing an `Id` in a static and lazily loading it.
9+
pub struct CachedId<T: Message> {
10+
ptr: AtomicPtr<T>,
11+
}
12+
13+
impl<T: Message> CachedId<T> {
14+
/// Constructs a new [`CachedId`].
15+
pub const fn new() -> Self {
16+
Self {
17+
ptr: AtomicPtr::new(ptr::null_mut()),
18+
}
19+
}
20+
21+
/// Returns the cached object. If no object is yet cached, creates one
22+
/// from the given closure and stores it.
23+
#[inline]
24+
pub fn get(&self, f: impl FnOnce() -> Id<T, Shared>) -> &'static T {
25+
// TODO: Investigate if we can use weaker orderings.
26+
let ptr = self.ptr.load(Ordering::SeqCst);
27+
// SAFETY: The pointer is either NULL, or has been created below.
28+
unsafe { ptr.as_ref() }.unwrap_or_else(|| {
29+
// "Forget" about releasing the object, effectively promoting it
30+
// to a static.
31+
let s = ManuallyDrop::new(f());
32+
let ptr = Id::as_ptr(&s);
33+
self.ptr.store(ptr as *mut T, Ordering::SeqCst);
34+
// SAFETY: The pointer is valid, and will always be valid, since
35+
// we haven't released it.
36+
unsafe { ptr.as_ref().unwrap_unchecked() }
37+
})
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
mod cached;
12
mod ns_string;
23

4+
pub use self::cached::CachedId;
35
pub use self::ns_string::*;

crates/icrate/src/Foundation/__macro_helpers/ns_string.rs

+2-38
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,8 @@
1414
//! See also the following crates that implement UTF-16 conversion:
1515
//! `utf16_lit`, `windows`, `const_utf16`, `wide-literals`, ...
1616
use core::ffi::c_void;
17-
use core::mem::ManuallyDrop;
18-
use core::ptr;
19-
use core::sync::atomic::{AtomicPtr, Ordering};
2017

2118
use crate::Foundation::NSString;
22-
use objc2::rc::Id;
2319
use objc2::runtime::Class;
2420

2521
// This is defined in CoreFoundation, but we don't emit a link attribute
@@ -101,7 +97,8 @@ impl CFConstString {
10197
}
10298

10399
// This is deliberately not `const` to prevent the result from being used
104-
// in other statics, since not all platforms support that (yet).
100+
// in other statics, since that is only possible if the
101+
// `unstable-static-nsstring` feature is enabled.
105102
#[inline]
106103
pub fn as_nsstring(&self) -> &NSString {
107104
self.as_nsstring_const()
@@ -206,39 +203,6 @@ const fn decode_utf8(s: &[u8], i: usize) -> (usize, u32) {
206203
}
207204
}
208205

209-
/// Allows storing a [`NSString`] in a static and lazily loading it.
210-
pub struct CachedNSString {
211-
ptr: AtomicPtr<NSString>,
212-
}
213-
214-
impl CachedNSString {
215-
/// Constructs a new [`CachedNSString`].
216-
pub const fn new() -> Self {
217-
Self {
218-
ptr: AtomicPtr::new(ptr::null_mut()),
219-
}
220-
}
221-
222-
/// Returns the cached NSString. If no string is yet cached, creates one
223-
/// with the given name and stores it.
224-
#[inline]
225-
pub fn get(&self, s: &str) -> &'static NSString {
226-
// TODO: Investigate if we can use weaker orderings.
227-
let ptr = self.ptr.load(Ordering::SeqCst);
228-
// SAFETY: The pointer is either NULL, or has been created below.
229-
unsafe { ptr.as_ref() }.unwrap_or_else(|| {
230-
// "Forget" about releasing the string, effectively promoting it
231-
// to a static.
232-
let s = ManuallyDrop::new(NSString::from_str(s));
233-
let ptr = Id::as_ptr(&s);
234-
self.ptr.store(ptr as *mut NSString, Ordering::SeqCst);
235-
// SAFETY: The pointer is valid, and will always be valid, since
236-
// we haven't released it.
237-
unsafe { ptr.as_ref().unwrap_unchecked() }
238-
})
239-
}
240-
}
241-
242206
#[cfg(test)]
243207
mod tests {
244208
use super::*;

crates/icrate/src/Foundation/macros/ns_string.rs

+44-45
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,71 @@
11
#![cfg(feature = "Foundation_NSString")]
2-
/// Creates an [`NSString`][`crate::Foundation::NSString`] from a static string.
2+
/// Create a [`NSString`] from a static [`str`].
33
///
4-
/// Note: This works by placing statics in special sections, which may not
5-
/// work completely reliably yet, see [#258]; until then, you should be
6-
/// careful about using this in libraries intended for others to consume.
4+
/// Equivalent to the [boxed C-strings] `@"string"` syntax in Objective-C.
5+
///
6+
/// [`NSString`]: crate::Foundation::NSString
7+
/// [boxed C-strings]: https://clang.llvm.org/docs/ObjectiveCLiterals.html#boxed-c-strings
8+
///
9+
///
10+
/// # Specification
11+
///
12+
/// The macro takes any expression that evaluates to a `const` `&str`, and
13+
/// produces a `&'static NSString`.
14+
///
15+
/// The returned string's encoding is not guaranteed to be UTF-8.
16+
///
17+
/// Strings containing ASCII NUL is allowed, the NUL is preserved as one would
18+
/// expect.
19+
///
20+
///
21+
/// # Cargo features
22+
///
23+
/// If the experimental `"unstable-static-nsstring"` feature is enabled, this
24+
/// will emit statics placed in special sections that will be replaced by dyld
25+
/// when the program starts up - which will in turn will cause the runtime
26+
/// cost of this macro to be completely non-existant!
27+
///
28+
/// However, it is known to not be completely reliable yet, see [#258] for
29+
/// details.
730
///
831
/// [#258]: https://github.com/madsmtm/objc2/issues/258
932
///
1033
///
1134
/// # Examples
1235
///
13-
/// This macro takes a either a `"string"` literal or `const` string slice as
14-
/// the argument, and produces a `&'static NSString`:
36+
/// Creating a static `NSString`.
1537
///
1638
/// ```
1739
/// use icrate::ns_string;
1840
/// use icrate::Foundation::NSString;
1941
///
2042
/// let hello: &'static NSString = ns_string!("hello");
2143
/// assert_eq!(hello.to_string(), "hello");
22-
///
23-
/// const WORLD: &str = "world";
24-
/// let world = ns_string!(WORLD);
25-
/// assert_eq!(world.to_string(), WORLD);
2644
/// ```
2745
///
46+
/// Creating a `NSString` from a `const` `&str`.
2847
///
29-
/// # Unicode Strings
48+
/// ```
49+
/// # use icrate::ns_string;
50+
/// const EXAMPLE: &str = "example";
51+
/// assert_eq!(ns_string!(EXAMPLE).to_string(), EXAMPLE);
52+
/// ```
3053
///
31-
/// An NSString can contain strings with many different encodings, including
32-
/// ASCII, UTF-8, UTF-16, and so on. This macro automatically converts your
33-
/// string to the most efficient encoding, you don't have to do anything!
54+
/// Creating unicode strings.
3455
///
3556
/// ```
3657
/// # use icrate::ns_string;
3758
/// let hello_ru = ns_string!("Привет");
3859
/// assert_eq!(hello_ru.to_string(), "Привет");
3960
/// ```
4061
///
41-
/// Note that because this is implemented with `const` evaluation, massive
42-
/// strings can increase compile time, and may even hit the `const` evaluation
43-
/// limit.
44-
///
45-
///
46-
/// # NUL handling
47-
///
48-
/// Strings containing ASCII NUL is allowed, the NUL is preserved as one would
49-
/// expect:
62+
/// Creating a string containing a NUL byte:
5063
///
5164
/// ```
5265
/// # use icrate::ns_string;
53-
/// let example = ns_string!("example\0");
54-
/// assert_eq!(example.to_string(), "example\0");
55-
///
56-
/// let example = ns_string!("exa\0mple");
57-
/// assert_eq!(example.to_string(), "exa\0mple");
66+
/// assert_eq!(ns_string!("example\0").to_string(), "example\0");
67+
/// assert_eq!(ns_string!("exa\0mple").to_string(), "exa\0mple");
5868
/// ```
59-
///
60-
///
61-
/// # Runtime Cost
62-
///
63-
/// None.
64-
///
65-
/// The result is equivalent to `@"string"` syntax in Objective-C.
66-
///
67-
/// Because of that, this should be preferred over [`NSString::from_str`]
68-
/// where possible.
69-
///
70-
/// [`NSString::from_str`]: crate::Foundation::NSString::from_str
7169
#[cfg(feature = "Foundation_NSString")] // For auto_doc_cfg
7270
#[macro_export]
7371
macro_rules! ns_string {
@@ -79,7 +77,7 @@ macro_rules! ns_string {
7977
}
8078

8179
#[doc(hidden)]
82-
#[cfg(feature = "apple")]
80+
#[cfg(all(feature = "apple", feature = "unstable-static-nsstring"))]
8381
#[macro_export]
8482
macro_rules! __ns_string_inner {
8583
($inp:ident) => {{
@@ -177,12 +175,13 @@ macro_rules! __ns_string_inner {
177175
}
178176

179177
#[doc(hidden)]
180-
#[cfg(not(feature = "apple"))]
178+
#[cfg(not(all(feature = "apple", feature = "unstable-static-nsstring")))]
181179
#[macro_export]
182180
macro_rules! __ns_string_inner {
183181
($inp:ident) => {{
184-
use $crate::Foundation::__macro_helpers::CachedNSString;
185-
static CACHED_NSSTRING: CachedNSString = CachedNSString::new();
186-
CACHED_NSSTRING.get($inp)
182+
static CACHED_NSSTRING: $crate::Foundation::__macro_helpers::CachedId<
183+
$crate::Foundation::NSString,
184+
> = $crate::Foundation::__macro_helpers::CachedId::new();
185+
CACHED_NSSTRING.get(|| $crate::Foundation::NSString::from_str($inp))
187186
}};
188187
}

crates/test-assembly/crates/test_ns_string/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,4 @@ Foundation = ["icrate/Foundation"]
2626
Foundation_NSString = ["icrate/Foundation_NSString"]
2727

2828
# Hack
29-
assembly-features = ["Foundation", "Foundation_NSString"]
29+
assembly-features = ["Foundation", "Foundation_NSString", "icrate/unstable-static-nsstring"]

crates/test-assembly/crates/test_ns_string/expected/gnustep-x86.s

+18-18
Original file line numberDiff line numberDiff line change
@@ -99,43 +99,43 @@ get_with_nul:
9999
.Lfunc_end2:
100100
.size get_with_nul, .Lfunc_end2-get_with_nul
101101

102-
.type .Lanon.[ID].0,@object
103-
.section .rodata..Lanon.[ID].0,"a",@progbits
104-
.Lanon.[ID].0:
105-
.ascii "abc"
106-
.size .Lanon.[ID].0, 3
107-
108-
.type .Lanon.[ID].1,@object
109-
.section .rodata..Lanon.[ID].1,"a",@progbits
110-
.Lanon.[ID].1:
111-
.ascii "\303\241b\304\207"
112-
.size .Lanon.[ID].1, 5
113-
114-
.type .Lanon.[ID].2,@object
115-
.section .rodata..Lanon.[ID].2,"a",@progbits
116-
.Lanon.[ID].2:
117-
.asciz "a\000b\000c"
118-
.size .Lanon.[ID].2, 6
119-
120102
.type SYM(test_ns_string[CRATE_ID]::get_ascii::CACHED_NSSTRING, 0).0,@object
121103
.section .bss.SYM(test_ns_string[CRATE_ID]::get_ascii::CACHED_NSSTRING, 0).0,"aw",@nobits
122104
.p2align 2
123105
SYM(test_ns_string[CRATE_ID]::get_ascii::CACHED_NSSTRING, 0).0:
124106
.long 0
125107
.size SYM(test_ns_string[CRATE_ID]::get_ascii::CACHED_NSSTRING, 0).0, 4
126108

109+
.type .Lanon.[ID].0,@object
110+
.section .rodata..Lanon.[ID].0,"a",@progbits
111+
.Lanon.[ID].0:
112+
.ascii "abc"
113+
.size .Lanon.[ID].0, 3
114+
127115
.type SYM(test_ns_string[CRATE_ID]::get_utf16::CACHED_NSSTRING, 0).0,@object
128116
.section .bss.SYM(test_ns_string[CRATE_ID]::get_utf16::CACHED_NSSTRING, 0).0,"aw",@nobits
129117
.p2align 2
130118
SYM(test_ns_string[CRATE_ID]::get_utf16::CACHED_NSSTRING, 0).0:
131119
.long 0
132120
.size SYM(test_ns_string[CRATE_ID]::get_utf16::CACHED_NSSTRING, 0).0, 4
133121

122+
.type .Lanon.[ID].1,@object
123+
.section .rodata..Lanon.[ID].1,"a",@progbits
124+
.Lanon.[ID].1:
125+
.ascii "\303\241b\304\207"
126+
.size .Lanon.[ID].1, 5
127+
134128
.type SYM(test_ns_string[CRATE_ID]::get_with_nul::CACHED_NSSTRING, 0).0,@object
135129
.section .bss.SYM(test_ns_string[CRATE_ID]::get_with_nul::CACHED_NSSTRING, 0).0,"aw",@nobits
136130
.p2align 2
137131
SYM(test_ns_string[CRATE_ID]::get_with_nul::CACHED_NSSTRING, 0).0:
138132
.long 0
139133
.size SYM(test_ns_string[CRATE_ID]::get_with_nul::CACHED_NSSTRING, 0).0, 4
140134

135+
.type .Lanon.[ID].2,@object
136+
.section .rodata..Lanon.[ID].2,"a",@progbits
137+
.Lanon.[ID].2:
138+
.asciz "a\000b\000c"
139+
.size .Lanon.[ID].2, 6
140+
141141
.section ".note.GNU-stack","",@progbits

0 commit comments

Comments
 (0)