objc2_foundation/
value.rs

1use alloc::ffi::CString;
2use alloc::string::ToString;
3use core::ffi::CStr;
4use core::fmt;
5use core::hash;
6use core::mem::MaybeUninit;
7use core::ptr::NonNull;
8use core::str;
9
10use objc2::encode::Encode;
11use objc2::rc::Retained;
12use objc2::AnyThread;
13
14use crate::NSValue;
15
16// We can't implement any auto traits for NSValue, since it can contain an
17// arbitrary object!
18
19/// Creation methods.
20impl NSValue {
21    /// Create a new `NSValue` containing the given type.
22    ///
23    /// Be careful when using this since you may accidentally pass a reference
24    /// when you wanted to pass a concrete type instead.
25    ///
26    ///
27    /// # Examples
28    ///
29    /// Create an `NSValue` containing an `i32`.
30    ///
31    /// ```
32    /// use objc2_foundation::NSValue;
33    ///
34    /// let val = NSValue::new(42i32);
35    /// ```
36    ///
37    /// [`NSPoint`]: crate::NSPoint
38    pub fn new<T: 'static + Copy + Encode>(value: T) -> Retained<Self> {
39        let bytes: NonNull<T> = NonNull::from(&value);
40        let encoding = CString::new(T::ENCODING.to_string()).unwrap();
41        unsafe {
42            Self::initWithBytes_objCType(
43                Self::alloc(),
44                bytes.cast(),
45                NonNull::new(encoding.as_ptr() as *mut _).unwrap(),
46            )
47        }
48    }
49}
50
51/// Getter methods.
52impl NSValue {
53    /// Retrieve the data contained in the `NSValue`.
54    ///
55    ///
56    /// # Safety
57    ///
58    /// The type of `T` must be what the NSValue actually stores, and any
59    /// safety invariants that the value has must be upheld.
60    ///
61    /// Note that it may be enough, although is not always, to check whether
62    /// [`contains_encoding`] returns `true`. For example, `NonNull<T>` have
63    /// the same encoding as `*const T`, but `NonNull<T>` is clearly not
64    /// safe to return from this function even if you've checked the encoding
65    /// beforehand.
66    ///
67    /// [`contains_encoding`]: Self::contains_encoding
68    ///
69    ///
70    /// # Examples
71    ///
72    /// Store a pointer in `NSValue`, and retrieve it again afterwards.
73    ///
74    /// ```
75    /// use std::ffi::c_void;
76    /// use std::ptr;
77    /// use objc2_foundation::NSValue;
78    ///
79    /// let val = NSValue::new::<*const c_void>(ptr::null());
80    /// // SAFETY: The value was just created with a pointer
81    /// let res = unsafe { val.get::<*const c_void>() };
82    /// assert!(res.is_null());
83    /// ```
84    pub unsafe fn get<T: 'static + Copy + Encode>(&self) -> T {
85        debug_assert!(
86        self.contains_encoding::<T>(),
87        "wrong encoding. NSValue tried to return something with encoding {}, but the encoding of the given type was {}",
88        self.encoding().unwrap_or("(NULL)"),
89        T::ENCODING,
90    );
91        let mut value = MaybeUninit::<T>::uninit();
92        let ptr: NonNull<T> = NonNull::new(value.as_mut_ptr()).unwrap();
93        #[allow(deprecated)]
94        unsafe {
95            self.getValue(ptr.cast())
96        };
97        // SAFETY: We know that `getValue:` initialized the value, and user
98        // ensures that it is safe to access.
99        unsafe { value.assume_init() }
100    }
101
102    #[cfg(feature = "NSRange")]
103    pub fn get_range(&self) -> Option<crate::NSRange> {
104        if self.contains_encoding::<crate::NSRange>() {
105            // SAFETY: We just checked that this contains an NSRange
106            Some(unsafe { self.rangeValue() })
107        } else {
108            None
109        }
110    }
111
112    #[cfg(all(feature = "NSGeometry", feature = "objc2-core-foundation"))]
113    pub fn get_point(&self) -> Option<crate::NSPoint> {
114        if self.contains_encoding::<crate::NSPoint>() {
115            // SAFETY: We just checked that this contains an NSPoint
116            //
117            // Note: The documentation says that `pointValue`, `sizeValue` and
118            // `rectValue` is only available on macOS, but turns out that they
119            // are actually available everywhere!
120            Some(unsafe { self.pointValue() })
121        } else {
122            None
123        }
124    }
125
126    #[cfg(all(feature = "NSGeometry", feature = "objc2-core-foundation"))]
127    pub fn get_size(&self) -> Option<crate::NSSize> {
128        if self.contains_encoding::<crate::NSSize>() {
129            // SAFETY: We just checked that this contains an NSSize
130            Some(unsafe { self.sizeValue() })
131        } else {
132            None
133        }
134    }
135
136    #[cfg(all(feature = "NSGeometry", feature = "objc2-core-foundation"))]
137    pub fn get_rect(&self) -> Option<crate::NSRect> {
138        if self.contains_encoding::<crate::NSRect>() {
139            // SAFETY: We just checked that this contains an NSRect
140            Some(unsafe { self.rectValue() })
141        } else {
142            None
143        }
144    }
145
146    pub fn encoding(&self) -> Option<&str> {
147        let ptr = self.objCType().as_ptr();
148        Some(unsafe { CStr::from_ptr(ptr) }.to_str().unwrap())
149    }
150
151    pub fn contains_encoding<T: 'static + Copy + Encode>(&self) -> bool {
152        T::ENCODING.equivalent_to_str(self.encoding().unwrap())
153    }
154}
155
156impl hash::Hash for NSValue {
157    #[inline]
158    fn hash<H: hash::Hasher>(&self, state: &mut H) {
159        (**self).hash(state);
160    }
161}
162
163impl PartialEq for NSValue {
164    #[doc(alias = "isEqualToValue:")]
165    fn eq(&self, other: &Self) -> bool {
166        // Use isEqualToValue: instead of isEqual: since it is faster
167        self.isEqualToValue(other)
168    }
169}
170
171impl Eq for NSValue {}
172
173impl fmt::Debug for NSValue {
174    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175        let enc = self.encoding().unwrap_or("(NULL)");
176        let bytes = &**self; // Delegate to -[NSObject description]
177        f.debug_struct("NSValue")
178            .field("encoding", &enc)
179            .field("bytes", bytes)
180            .finish()
181    }
182}