objc2/runtime/
protocol_object.rs

1use core::fmt;
2use core::hash;
3use core::marker::PhantomData;
4use core::ptr::NonNull;
5
6use crate::encode::{Encoding, RefEncode};
7use crate::rc::{autoreleasepool_leaking, Retained};
8use crate::runtime::__nsstring::nsstring_to_str;
9use crate::runtime::{AnyObject, NSObjectProtocol};
10use crate::Message;
11
12/// An internal helper trait for [`ProtocolObject`].
13///
14///
15/// # Safety
16///
17/// This is meant to be a sealed trait, and should not be implemented outside
18/// of the [`extern_protocol!`] macro.
19///
20/// [`extern_protocol!`]: crate::extern_protocol
21pub unsafe trait ImplementedBy<T: ?Sized + Message> {
22    #[doc(hidden)]
23    const __INNER: ();
24}
25
26/// An object representing any object that implements a specified protocol.
27///
28/// Objective-C has [a feature][protocol-type-checking] where you can write
29/// `id<MyProtocol>`, and then work with the protocol as-if it was an object;
30/// this is very similar to `dyn` traits in Rust!
31///
32/// If we could customize how `dyn Trait` works, then this struct would not
33/// have been necessary; however, `dyn Trait` is a wide pointer with overhead,
34/// which this struct helps avoid.
35///
36/// If the trait `T` inherits [`NSObjectProtocol`], this will implement common
37/// traits like `Debug`, `PartialEq`, `Eq` and `Hash`.
38///
39/// [protocol-type-checking]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProtocols.html#//apple_ref/doc/uid/TP30001163-CH15-TPXREF151
40///
41///
42/// # Example
43///
44/// Convert an object `MyObject` that implements the a protocol `MyProtocol`
45/// into a [`ProtocolObject`] for working with the protocol in a type-erased
46/// way.
47///
48/// ```
49/// use objc2::runtime::ProtocolObject;
50/// use objc2::rc::Retained;
51/// # use objc2::runtime::NSObject as MyObject;
52/// # use objc2::runtime::NSObjectProtocol as MyProtocol;
53///
54/// let obj: Retained<MyObject> = MyObject::new();
55/// let proto: &ProtocolObject<dyn MyProtocol> = ProtocolObject::from_ref(&*obj);
56/// let proto: Retained<ProtocolObject<dyn MyProtocol>> = ProtocolObject::from_retained(obj);
57/// ```
58#[doc(alias = "id")]
59#[repr(C)]
60pub struct ProtocolObject<P: ?Sized> {
61    inner: AnyObject,
62    p: PhantomData<P>,
63}
64
65// SAFETY: `Send` if the underlying trait promises `Send`.
66//
67// E.g. `ProtocolObject<dyn NSObjectProtocol + Send>` is naturally `Send`.
68unsafe impl<P: ?Sized + Send> Send for ProtocolObject<P> {}
69
70// SAFETY: `Sync` if the underlying trait promises `Sync`.
71//
72// E.g. `ProtocolObject<dyn NSObjectProtocol + Sync>` is naturally `Sync`.
73unsafe impl<P: ?Sized + Sync> Sync for ProtocolObject<P> {}
74
75// SAFETY: The type is `#[repr(C)]` and `AnyObject` internally
76unsafe impl<P: ?Sized> RefEncode for ProtocolObject<P> {
77    const ENCODING_REF: Encoding = Encoding::Object;
78}
79
80// SAFETY: The type is `AnyObject` internally, and is mean to be messaged
81// as-if it's an object.
82unsafe impl<P: ?Sized> Message for ProtocolObject<P> {}
83
84impl<P: ?Sized> ProtocolObject<P> {
85    /// Get an immutable type-erased reference from a type implementing a
86    /// protocol.
87    #[inline]
88    pub fn from_ref<T: ?Sized + Message>(obj: &T) -> &Self
89    where
90        P: ImplementedBy<T>,
91    {
92        let ptr: NonNull<T> = NonNull::from(obj);
93        let ptr: NonNull<Self> = ptr.cast();
94        // SAFETY: Implementer ensures that the object conforms to the
95        // protocol; so converting the reference here is safe.
96        unsafe { ptr.as_ref() }
97    }
98
99    /// Get a type-erased object from a type implementing a protocol.
100    #[deprecated = "use `ProtocolObject::from_retained` instead"]
101    #[inline]
102    pub fn from_id<T>(obj: Retained<T>) -> Retained<Self>
103    where
104        P: ImplementedBy<T> + 'static,
105        T: Message + 'static,
106    {
107        Self::from_retained(obj)
108    }
109
110    /// Get a type-erased object from a type implementing a protocol.
111    #[inline]
112    pub fn from_retained<T>(obj: Retained<T>) -> Retained<Self>
113    where
114        P: ImplementedBy<T> + 'static,
115        T: Message + 'static,
116    {
117        // SAFETY:
118        // - The type can be represented as the casted-to type.
119        // - Both types are `'static` (this could maybe be relaxed a bit, but
120        //   let's be on the safe side)!
121        unsafe { Retained::cast_unchecked::<Self>(obj) }
122    }
123}
124
125impl<P: ?Sized + NSObjectProtocol> PartialEq for ProtocolObject<P> {
126    #[inline]
127    #[doc(alias = "isEqual:")]
128    fn eq(&self, other: &Self) -> bool {
129        self.isEqual(Some(&other.inner))
130    }
131}
132
133impl<P: ?Sized + NSObjectProtocol> Eq for ProtocolObject<P> {}
134
135impl<P: ?Sized + NSObjectProtocol> hash::Hash for ProtocolObject<P> {
136    #[inline]
137    fn hash<H: hash::Hasher>(&self, state: &mut H) {
138        <Self as NSObjectProtocol>::hash(self).hash(state);
139    }
140}
141
142impl<P: ?Sized + NSObjectProtocol> fmt::Debug for ProtocolObject<P> {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        let description = self.description();
145
146        // `NSString`s in return types, such as the one in `description`, are
147        // in general _supposed_ to be immutable:
148        // <https://developer.apple.com/library/archive/documentation/General/Conceptual/CocoaEncyclopedia/ObjectMutability/ObjectMutability.html#//apple_ref/doc/uid/TP40010810-CH5-SW65>
149        //
150        // In reality, that isn't actually the case for `NSMutableString`,
151        // the `description` of that just returns itself instead of copying.
152        //
153        // Luckily though, the `UTF8String` returned by mutable objects do not
154        // return a reference to internal data, and instead always allocate
155        // a new autoreleased object (see details in `nsstring_to_str`), so
156        // we do not have to worry about the string being mutated in e.g. a
157        // malicious `Write` implementation in `fmt::Formatter` while we hold
158        // the `&str`.
159
160        // We use a leaking autorelease pool since often the string will be
161        // UTF-8, and in that case the pool will be irrelevant. Also, it
162        // allows us to pass the formatter into the pool (since it may contain
163        // a pool internally that it assumes is current when writing).
164        autoreleasepool_leaking(|pool| {
165            // SAFETY:
166            // - The `description` selector is guaranteed to always return an
167            //   instance of `NSString`.
168            // - We control the scope in which the string is alive, so we know
169            //   it is not moved outside the current autorelease pool
170            //   (`autoreleasepool_leaking` is greatly helping with this,
171            //   though by itself does not fully ensure it).
172            let s = unsafe { nsstring_to_str(&description, pool) };
173            fmt::Display::fmt(s, f)
174        })
175    }
176}
177
178impl<P: ?Sized, T> AsRef<ProtocolObject<T>> for ProtocolObject<P>
179where
180    T: ?Sized + ImplementedBy<ProtocolObject<P>>,
181{
182    #[inline]
183    fn as_ref(&self) -> &ProtocolObject<T> {
184        ProtocolObject::from_ref(self)
185    }
186}
187
188// TODO: Maybe implement Borrow?
189
190impl<P: ?Sized + 'static> AsRef<AnyObject> for ProtocolObject<P> {
191    #[inline]
192    fn as_ref(&self) -> &AnyObject {
193        let ptr: NonNull<ProtocolObject<P>> = NonNull::from(self);
194        let ptr: NonNull<AnyObject> = ptr.cast();
195        // SAFETY: All protocol objects are Objective-C objects too.
196        unsafe { ptr.as_ref() }
197    }
198}
199
200#[cfg(test)]
201#[allow(clippy::missing_safety_doc)]
202#[allow(dead_code)]
203mod tests {
204    use alloc::format;
205    use core::ffi::CStr;
206
207    use static_assertions::{assert_impl_all, assert_not_impl_any};
208
209    use super::*;
210    use crate::runtime::{ClassBuilder, NSObject};
211    use crate::{define_class, extern_methods, extern_protocol, msg_send, ClassType};
212
213    extern_protocol!(
214        unsafe trait Foo {
215            #[unsafe(method(foo))]
216            fn foo_class();
217
218            #[unsafe(method(foo))]
219            fn foo_instance(&self);
220        }
221    );
222
223    extern_protocol!(
224        unsafe trait Bar: NSObjectProtocol {
225            #[unsafe(method(bar))]
226            fn bar_class();
227
228            #[unsafe(method(bar))]
229            fn bar_instance(&self);
230        }
231    );
232
233    extern_protocol!(
234        unsafe trait FooBar: Foo + Bar {
235            #[unsafe(method(foobar))]
236            fn foobar_class();
237
238            #[unsafe(method(foobar))]
239            fn foobar_instance(&self);
240        }
241    );
242
243    extern_protocol!(
244        unsafe trait FooFooBar: Foo + FooBar {
245            #[unsafe(method(foofoobar))]
246            fn foofoobar_class();
247
248            #[unsafe(method(foofoobar))]
249            fn foofoobar_instance(&self);
250        }
251    );
252
253    define_class!(
254        #[unsafe(super(NSObject))]
255        #[derive(Debug, PartialEq, Eq, Hash)]
256        struct DummyClass;
257
258        unsafe impl NSObjectProtocol for DummyClass {}
259    );
260
261    unsafe impl Foo for DummyClass {}
262    unsafe impl Bar for DummyClass {}
263    unsafe impl FooBar for DummyClass {}
264    // unsafe impl FooFooBar for DummyClass {}
265
266    impl DummyClass {
267        extern_methods!(
268            #[unsafe(method(new))]
269            fn new() -> Retained<Self>;
270        );
271    }
272
273    #[test]
274    fn impl_traits() {
275        assert_impl_all!(NSObject: NSObjectProtocol);
276        assert_impl_all!(ProtocolObject<dyn NSObjectProtocol>: NSObjectProtocol);
277        assert_not_impl_any!(ProtocolObject<dyn NSObjectProtocol>: Send, Sync);
278        assert_impl_all!(ProtocolObject<dyn NSObjectProtocol + Send>: NSObjectProtocol, Send);
279        assert_not_impl_any!(ProtocolObject<dyn NSObjectProtocol + Send>: Sync);
280        assert_impl_all!(ProtocolObject<dyn NSObjectProtocol + Sync>: NSObjectProtocol, Sync);
281        assert_not_impl_any!(ProtocolObject<dyn NSObjectProtocol + Sync>: Send);
282        assert_impl_all!(ProtocolObject<dyn NSObjectProtocol + Send + Sync>: NSObjectProtocol, Send, Sync);
283        assert_not_impl_any!(ProtocolObject<dyn Foo>: NSObjectProtocol);
284        assert_impl_all!(ProtocolObject<dyn Bar>: NSObjectProtocol);
285        assert_impl_all!(ProtocolObject<dyn FooBar>: NSObjectProtocol);
286        assert_impl_all!(ProtocolObject<dyn FooFooBar>: NSObjectProtocol);
287        assert_impl_all!(DummyClass: NSObjectProtocol);
288
289        assert_not_impl_any!(NSObject: Foo);
290        assert_not_impl_any!(ProtocolObject<dyn NSObjectProtocol>: Foo);
291        assert_impl_all!(ProtocolObject<dyn Foo>: Foo);
292        assert_not_impl_any!(ProtocolObject<dyn Bar>: Foo);
293        assert_impl_all!(ProtocolObject<dyn FooBar>: Foo);
294        assert_impl_all!(ProtocolObject<dyn FooFooBar>: Foo);
295        assert_impl_all!(DummyClass: Foo);
296
297        assert_not_impl_any!(NSObject: Bar);
298        assert_not_impl_any!(ProtocolObject<dyn NSObjectProtocol>: Bar);
299        assert_not_impl_any!(ProtocolObject<dyn Foo>: Bar);
300        assert_impl_all!(ProtocolObject<dyn Bar>: Bar);
301        assert_impl_all!(ProtocolObject<dyn FooBar>: Bar);
302        assert_impl_all!(ProtocolObject<dyn FooFooBar>: Bar);
303        assert_impl_all!(DummyClass: Bar);
304
305        assert_not_impl_any!(NSObject: FooBar);
306        assert_not_impl_any!(ProtocolObject<dyn NSObjectProtocol>: FooBar);
307        assert_not_impl_any!(ProtocolObject<dyn Foo>: FooBar);
308        assert_not_impl_any!(ProtocolObject<dyn Bar>: FooBar);
309        assert_impl_all!(ProtocolObject<dyn FooBar>: FooBar);
310        assert_impl_all!(ProtocolObject<dyn FooFooBar>: FooBar);
311        assert_impl_all!(DummyClass: FooBar);
312
313        assert_not_impl_any!(NSObject: FooFooBar);
314        assert_not_impl_any!(ProtocolObject<dyn NSObjectProtocol>: FooFooBar);
315        assert_not_impl_any!(ProtocolObject<dyn Foo>: FooFooBar);
316        assert_not_impl_any!(ProtocolObject<dyn Bar>: FooFooBar);
317        assert_not_impl_any!(ProtocolObject<dyn FooBar>: FooFooBar);
318        assert_impl_all!(ProtocolObject<dyn FooFooBar>: FooFooBar);
319        assert_not_impl_any!(DummyClass: FooFooBar);
320    }
321
322    #[test]
323    fn convertible() {
324        let obj = DummyClass::new();
325        let foobar: &ProtocolObject<dyn FooBar> = ProtocolObject::from_ref(&*obj);
326        let foobar: &ProtocolObject<dyn FooBar> = ProtocolObject::from_ref(foobar);
327
328        let _bar: &ProtocolObject<dyn Bar> = ProtocolObject::from_ref(foobar);
329        let bar: &ProtocolObject<dyn Bar> = ProtocolObject::from_ref(&*obj);
330        let bar: &ProtocolObject<dyn Bar> = ProtocolObject::from_ref(bar);
331
332        let _foo: &ProtocolObject<dyn Foo> = ProtocolObject::from_ref(foobar);
333        let foo: &ProtocolObject<dyn Foo> = ProtocolObject::from_ref(&*obj);
334        let _foo: &ProtocolObject<dyn Foo> = ProtocolObject::from_ref(foo);
335
336        let _nsobject: &ProtocolObject<dyn NSObjectProtocol> = ProtocolObject::from_ref(foobar);
337        let _nsobject: &ProtocolObject<dyn NSObjectProtocol> = ProtocolObject::from_ref(bar);
338        let nsobject: &ProtocolObject<dyn NSObjectProtocol> = ProtocolObject::from_ref(&*obj);
339        let _nsobject: &ProtocolObject<dyn NSObjectProtocol> = ProtocolObject::from_ref(nsobject);
340        let _: &ProtocolObject<dyn NSObjectProtocol + Send> = ProtocolObject::from_ref(&*obj);
341        let _: &ProtocolObject<dyn NSObjectProtocol + Sync> = ProtocolObject::from_ref(&*obj);
342        let _: &ProtocolObject<dyn NSObjectProtocol + Send + Sync> =
343            ProtocolObject::from_ref(&*obj);
344
345        let _foobar: Retained<ProtocolObject<dyn FooBar>> = ProtocolObject::from_retained(obj);
346    }
347
348    #[test]
349    fn convert_to_anyobj() {
350        let obj = NSObject::new();
351        let obj: Retained<ProtocolObject<dyn NSObjectProtocol>> =
352            ProtocolObject::from_retained(obj);
353        let _obj: &AnyObject = obj.as_ref();
354    }
355
356    #[test]
357    fn test_traits() {
358        use core::hash::Hasher;
359        use std::collections::hash_map::DefaultHasher;
360        use std::hash::Hash;
361
362        let obj = DummyClass::new();
363        let obj2 = DummyClass::new();
364
365        let foobar: &ProtocolObject<dyn FooBar> = ProtocolObject::from_ref(&*obj);
366        let foobar2: &ProtocolObject<dyn FooBar> = ProtocolObject::from_ref(&*obj2);
367
368        assert_eq!(
369            format!("{obj:?}"),
370            format!("DummyClass {{ super: {foobar:?}, ivars: () }}"),
371        );
372        assert_eq!(obj == obj2, foobar == foobar2);
373
374        let mut hashstate_a = DefaultHasher::new();
375        let mut hashstate_b = DefaultHasher::new();
376
377        obj.hash(&mut hashstate_a);
378        <_ as Hash>::hash(foobar, &mut hashstate_b);
379
380        assert_eq!(hashstate_a.finish(), hashstate_b.finish());
381    }
382
383    // We use `debug_assertions` here just because it's something that we know
384    // our CI already tests.
385    extern_protocol!(
386        #[cfg(debug_assertions)]
387        unsafe trait CfgTest {}
388    );
389
390    #[test]
391    #[cfg(debug_assertions)]
392    fn test_meta() {
393        if false {
394            let _protocol = <dyn CfgTest as crate::ProtocolType>::protocol();
395        }
396    }
397
398    #[test]
399    #[cfg_attr(
400        feature = "gnustep-1-7",
401        ignore = "depends on the platform's NSString unicode handling"
402    )]
403    fn debug_non_utf8_classname() {
404        // Some class with invalid UTF-8 character inside
405        let s = CStr::from_bytes_with_nul(b"My\xF0\x90\x80Class\0").unwrap();
406
407        let cls = ClassBuilder::new(s, NSObject::class()).unwrap().register();
408        let obj: Retained<NSObject> = unsafe { msg_send![cls, new] };
409
410        let expected = format!("<My\u{f8ff}êÄClass: {:p}>", &*obj);
411        assert_eq!(format!("{obj:?}"), expected);
412    }
413}