#[cfg(feature = "NSObjCRuntime")] use core::cmp; use core::ffi::c_void; use core::fmt; use core::ops::AddAssign; use core::panic::RefUnwindSafe; use core::panic::UnwindSafe; use core::str; use objc2::msg_send; use objc2::rc::{autoreleasepool_leaking, Allocated, AutoreleasePool, Retained}; use objc2::runtime::__nsstring::{nsstring_len, nsstring_to_str, UTF8_ENCODING}; use objc2::{AnyThread, Message}; use crate::util; use crate::{NSMutableString, NSString}; // Even if an exception occurs inside a string method, the state of the string // (should) still be perfectly safe to access. impl UnwindSafe for NSString {} impl RefUnwindSafe for NSString {} impl NSString { /// The number of UTF-8 code units in `self`. #[doc(alias = "lengthOfBytesUsingEncoding")] #[doc(alias = "lengthOfBytesUsingEncoding:")] pub fn len(&self) -> usize { // SAFETY: This is an instance of `NSString` unsafe { nsstring_len(self) } } /// The number of UTF-16 code units in the string. /// /// See also [`NSString::len`]. #[doc(alias = "length")] pub fn len_utf16(&self) -> usize { self.length() } pub fn is_empty(&self) -> bool { // TODO: lengthOfBytesUsingEncoding: might sometimes return 0 for // other reasons, so this is not really correct! self.len() == 0 } /// Convert the string into a [string slice](`prim@str`). /// /// The signature of this method can be a bit confusing, as it contains /// several lifetimes; the lifetime `'s` of the `NSString`, the lifetime /// `'p` of the current autorelease pool and the lifetime `'r` of the /// returned string slice. /// /// In general, this method converts the string to a newly allocated UTF-8 /// string, autoreleases the buffer, and returns a slice pointer to this /// internal buffer, which will become invalid once the autorelease pool /// is popped. So the lifetime of the return value is bound to the current /// autorelease pool. /// /// However, as an optimization, this method may choose to instead return /// an internal reference to the `NSString` when it can, and when the /// string is immutable, and that is why the lifetime of the returned /// string slice is also bound to the string itself. /// /// You should prefer the [`to_string`] method or the /// [`Display` implementation][display-impl] over this method when /// possible. /// /// [`to_string`]: alloc::string::ToString::to_string /// [display-impl]: NSString#impl-Display-for-NSString /// /// /// # Safety /// /// The pool must be the innermost pool, see [the documentation on /// `autoreleasepool`][autoreleasepool]. /// /// [autoreleasepool]: objc2::rc::autoreleasepool /// /// /// # Examples /// /// Get the string slice of the `NSString`, and compare it with another /// inside an autorelease pool. /// /// ``` /// use objc2_foundation::NSString; /// use objc2::rc::autoreleasepool; /// /// let string = NSString::from_str("foo"); /// autoreleasepool(|pool| { /// // SAFETY: The str is not used outside the autorelease pool. /// assert_eq!(unsafe { string.to_str(pool) }, "foo"); /// }); /// ``` #[doc(alias = "UTF8String")] pub unsafe fn to_str<'r, 's: 'r, 'p: 'r>(&'s self, pool: AutoreleasePool<'p>) -> &'r str { // SAFETY: This is an instance of `NSString`. // // Caller upholds that the string is not moved outside the pool. unsafe { nsstring_to_str(self, pool) } } // TODO: Allow usecases where the NUL byte from `UTF8String` is kept? /// Creates an immutable `NSString` by copying the given string slice. /// /// Prefer using the [`ns_string!`] macro when possible. /// /// [`ns_string!`]: crate::ns_string #[doc(alias = "initWithBytes")] #[doc(alias = "initWithBytes:length:encoding:")] #[allow(clippy::should_implement_trait)] // Not really sure of a better name pub fn from_str(string: &str) -> Retained { unsafe { init_with_str(Self::alloc(), string) } } // TODO: `initWithBytesNoCopy:length:encoding:` from `&'static str`. } impl NSMutableString { /// Creates a new [`NSMutableString`] by copying the given string slice. #[doc(alias = "initWithBytes:length:encoding:")] #[allow(clippy::should_implement_trait)] // Not really sure of a better name pub fn from_str(string: &str) -> Retained { unsafe { init_with_str(Self::alloc(), string) } } } unsafe fn init_with_str(obj: Allocated, string: &str) -> Retained { let bytes: *const c_void = string.as_ptr().cast(); // We use `msg_send!` instead of the generated method, since that // assumes the encoding is `usize`, whereas GNUStep assumes `i32`. unsafe { msg_send![ obj, initWithBytes: bytes, length: string.len(), encoding: UTF8_ENCODING, ] } } impl PartialEq for NSMutableString { #[inline] fn eq(&self, other: &NSString) -> bool { PartialEq::eq(&**self, other) } } impl PartialEq for NSString { #[inline] fn eq(&self, other: &NSMutableString) -> bool { PartialEq::eq(self, &**other) } } #[cfg(feature = "NSObjCRuntime")] impl PartialOrd for NSString { #[inline] fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } #[cfg(feature = "NSObjCRuntime")] impl Ord for NSString { fn cmp(&self, other: &Self) -> cmp::Ordering { self.compare(other).into() } } #[cfg(feature = "NSObjCRuntime")] impl PartialOrd for NSMutableString { #[inline] fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } #[cfg(feature = "NSObjCRuntime")] impl PartialOrd for NSMutableString { #[inline] fn partial_cmp(&self, other: &NSString) -> Option { PartialOrd::partial_cmp(&**self, other) } } #[cfg(feature = "NSObjCRuntime")] impl PartialOrd for NSString { #[inline] fn partial_cmp(&self, other: &NSMutableString) -> Option { PartialOrd::partial_cmp(self, &**other) } } #[cfg(feature = "NSObjCRuntime")] impl Ord for NSMutableString { #[inline] fn cmp(&self, other: &Self) -> cmp::Ordering { Ord::cmp(&**self, &**other) } } // TODO: PartialEq and PartialOrd against &str // See `fruity`'s implementation: // https://github.com/nvzqz/fruity/blob/320efcf715c2c5fbd2f3084f671f2be2e03a6f2b/src/foundation/ns_string/mod.rs#L69-L163 impl AddAssign<&NSString> for &NSMutableString { #[inline] fn add_assign(&mut self, other: &NSString) { self.appendString(other); } } impl fmt::Display for NSString { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // SAFETY: The object is an instance of `NSString`. unsafe { util::display_string(self, f) } } } impl fmt::Debug for NSString { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // SAFETY: Same as for `display_string`: // - The object is an instance of `NSString`. // - We control the scope in which the string is alive, so we know // it is not moved outside the current autorelease pool. autoreleasepool_leaking(|pool| fmt::Debug::fmt(unsafe { nsstring_to_str(self, pool) }, f)) } } impl fmt::Display for NSMutableString { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&**self, f) } } impl fmt::Write for &NSMutableString { fn write_str(&mut self, s: &str) -> fmt::Result { let nsstring = NSString::from_str(s); self.appendString(&nsstring); Ok(()) } }