objc2_foundation/
string.rs

1#[cfg(feature = "NSObjCRuntime")]
2use core::cmp;
3use core::ffi::c_void;
4use core::fmt;
5use core::ops::AddAssign;
6use core::panic::RefUnwindSafe;
7use core::panic::UnwindSafe;
8use core::str;
9
10use objc2::msg_send;
11use objc2::rc::{autoreleasepool_leaking, Allocated, AutoreleasePool, Retained};
12use objc2::runtime::__nsstring::{nsstring_len, nsstring_to_str, UTF8_ENCODING};
13use objc2::{AnyThread, Message};
14
15use crate::util;
16use crate::{NSMutableString, NSString};
17
18// Even if an exception occurs inside a string method, the state of the string
19// (should) still be perfectly safe to access.
20impl UnwindSafe for NSString {}
21impl RefUnwindSafe for NSString {}
22
23impl NSString {
24    /// The number of UTF-8 code units in `self`.
25    #[doc(alias = "lengthOfBytesUsingEncoding")]
26    #[doc(alias = "lengthOfBytesUsingEncoding:")]
27    pub fn len(&self) -> usize {
28        // SAFETY: This is an instance of `NSString`
29        unsafe { nsstring_len(self) }
30    }
31
32    /// The number of UTF-16 code units in the string.
33    ///
34    /// See also [`NSString::len`].
35    #[doc(alias = "length")]
36    pub fn len_utf16(&self) -> usize {
37        self.length()
38    }
39
40    pub fn is_empty(&self) -> bool {
41        // TODO: lengthOfBytesUsingEncoding: might sometimes return 0 for
42        // other reasons, so this is not really correct!
43        self.len() == 0
44    }
45
46    /// Convert the string into a [string slice](`prim@str`).
47    ///
48    /// The signature of this method can be a bit confusing, as it contains
49    /// several lifetimes; the lifetime `'s` of the `NSString`, the lifetime
50    /// `'p` of the current autorelease pool and the lifetime `'r` of the
51    /// returned string slice.
52    ///
53    /// In general, this method converts the string to a newly allocated UTF-8
54    /// string, autoreleases the buffer, and returns a slice pointer to this
55    /// internal buffer, which will become invalid once the autorelease pool
56    /// is popped. So the lifetime of the return value is bound to the current
57    /// autorelease pool.
58    ///
59    /// However, as an optimization, this method may choose to instead return
60    /// an internal reference to the `NSString` when it can, and when the
61    /// string is immutable, and that is why the lifetime of the returned
62    /// string slice is also bound to the string itself.
63    ///
64    /// You should prefer the [`to_string`] method or the
65    /// [`Display` implementation][display-impl] over this method when
66    /// possible.
67    ///
68    /// [`to_string`]: alloc::string::ToString::to_string
69    /// [display-impl]: NSString#impl-Display-for-NSString
70    ///
71    ///
72    /// # Safety
73    ///
74    /// The pool must be the innermost pool, see [the documentation on
75    /// `autoreleasepool`][autoreleasepool].
76    ///
77    /// [autoreleasepool]: objc2::rc::autoreleasepool
78    ///
79    ///
80    /// # Examples
81    ///
82    /// Get the string slice of the `NSString`, and compare it with another
83    /// inside an autorelease pool.
84    ///
85    /// ```
86    /// use objc2_foundation::NSString;
87    /// use objc2::rc::autoreleasepool;
88    ///
89    /// let string = NSString::from_str("foo");
90    /// autoreleasepool(|pool| {
91    ///     // SAFETY: The str is not used outside the autorelease pool.
92    ///     assert_eq!(unsafe { string.to_str(pool) }, "foo");
93    /// });
94    /// ```
95    #[doc(alias = "UTF8String")]
96    pub unsafe fn to_str<'r, 's: 'r, 'p: 'r>(&'s self, pool: AutoreleasePool<'p>) -> &'r str {
97        // SAFETY: This is an instance of `NSString`.
98        //
99        // Caller upholds that the string is not moved outside the pool.
100        unsafe { nsstring_to_str(self, pool) }
101    }
102
103    // TODO: Allow usecases where the NUL byte from `UTF8String` is kept?
104
105    /// Creates an immutable `NSString` by copying the given string slice.
106    ///
107    /// Prefer using the [`ns_string!`] macro when possible.
108    ///
109    /// [`ns_string!`]: crate::ns_string
110    #[doc(alias = "initWithBytes")]
111    #[doc(alias = "initWithBytes:length:encoding:")]
112    #[allow(clippy::should_implement_trait)] // Not really sure of a better name
113    pub fn from_str(string: &str) -> Retained<Self> {
114        unsafe { init_with_str(Self::alloc(), string) }
115    }
116
117    // TODO: `initWithBytesNoCopy:length:encoding:` from `&'static str`.
118}
119
120impl NSMutableString {
121    /// Creates a new [`NSMutableString`] by copying the given string slice.
122    #[doc(alias = "initWithBytes:length:encoding:")]
123    #[allow(clippy::should_implement_trait)] // Not really sure of a better name
124    pub fn from_str(string: &str) -> Retained<Self> {
125        unsafe { init_with_str(Self::alloc(), string) }
126    }
127}
128
129unsafe fn init_with_str<T: Message>(obj: Allocated<T>, string: &str) -> Retained<T> {
130    let bytes: *const c_void = string.as_ptr().cast();
131    // We use `msg_send!` instead of the generated method, since that
132    // assumes the encoding is `usize`, whereas GNUStep assumes `i32`.
133    unsafe {
134        msg_send![
135            obj,
136            initWithBytes: bytes,
137            length: string.len(),
138            encoding: UTF8_ENCODING,
139        ]
140    }
141}
142
143impl PartialEq<NSString> for NSMutableString {
144    #[inline]
145    fn eq(&self, other: &NSString) -> bool {
146        PartialEq::eq(&**self, other)
147    }
148}
149
150impl PartialEq<NSMutableString> for NSString {
151    #[inline]
152    fn eq(&self, other: &NSMutableString) -> bool {
153        PartialEq::eq(self, &**other)
154    }
155}
156
157#[cfg(feature = "NSObjCRuntime")]
158impl PartialOrd for NSString {
159    #[inline]
160    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
161        Some(self.cmp(other))
162    }
163}
164
165#[cfg(feature = "NSObjCRuntime")]
166impl Ord for NSString {
167    fn cmp(&self, other: &Self) -> cmp::Ordering {
168        self.compare(other).into()
169    }
170}
171
172#[cfg(feature = "NSObjCRuntime")]
173impl PartialOrd for NSMutableString {
174    #[inline]
175    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
176        Some(self.cmp(other))
177    }
178}
179
180#[cfg(feature = "NSObjCRuntime")]
181impl PartialOrd<NSString> for NSMutableString {
182    #[inline]
183    fn partial_cmp(&self, other: &NSString) -> Option<cmp::Ordering> {
184        PartialOrd::partial_cmp(&**self, other)
185    }
186}
187
188#[cfg(feature = "NSObjCRuntime")]
189impl PartialOrd<NSMutableString> for NSString {
190    #[inline]
191    fn partial_cmp(&self, other: &NSMutableString) -> Option<cmp::Ordering> {
192        PartialOrd::partial_cmp(self, &**other)
193    }
194}
195
196#[cfg(feature = "NSObjCRuntime")]
197impl Ord for NSMutableString {
198    #[inline]
199    fn cmp(&self, other: &Self) -> cmp::Ordering {
200        Ord::cmp(&**self, &**other)
201    }
202}
203
204// TODO: PartialEq and PartialOrd against &str
205// See `fruity`'s implementation:
206// https://github.com/nvzqz/fruity/blob/320efcf715c2c5fbd2f3084f671f2be2e03a6f2b/src/foundation/ns_string/mod.rs#L69-L163
207
208impl AddAssign<&NSString> for &NSMutableString {
209    #[inline]
210    fn add_assign(&mut self, other: &NSString) {
211        self.appendString(other);
212    }
213}
214
215impl fmt::Display for NSString {
216    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217        // SAFETY: The object is an instance of `NSString`.
218        unsafe { util::display_string(self, f) }
219    }
220}
221
222impl fmt::Debug for NSString {
223    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224        // SAFETY: Same as for `display_string`:
225        // - The object is an instance of `NSString`.
226        // - We control the scope in which the string is alive, so we know
227        //   it is not moved outside the current autorelease pool.
228        autoreleasepool_leaking(|pool| fmt::Debug::fmt(unsafe { nsstring_to_str(self, pool) }, f))
229    }
230}
231
232impl fmt::Display for NSMutableString {
233    #[inline]
234    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235        fmt::Display::fmt(&**self, f)
236    }
237}
238
239impl fmt::Write for &NSMutableString {
240    fn write_str(&mut self, s: &str) -> fmt::Result {
241        let nsstring = NSString::from_str(s);
242        self.appendString(&nsstring);
243        Ok(())
244    }
245}