objc2_foundation/copying.rs
1use objc2::extern_protocol;
2use objc2::rc::Retained;
3use objc2::runtime::NSZone;
4use objc2::runtime::ProtocolObject;
5use objc2::Message;
6
7/// A helper type for implementing [`NSCopying`].
8///
9/// `NSCopying` and `NSMutableCopying` do not in their signatures describe the
10/// result type from the copying operation. This is problematic, as it means
11/// that using them ends up falling back to [`AnyObject`], which makes copying
12/// much less useful and ergonomic.
13///
14/// To properly describe this, we need an associated type which describes the
15/// actual result type from a copy. The associated type can't be present
16/// directly on the protocol traits themselves, however, since we want to use
17/// them as e.g. `ProtocolObject<dyn NSCopying>`, so we introduce this helper
18/// trait instead. See [`MutableCopyingHelper`] for the mutable variant.
19///
20/// We might be able to get rid of this hack once [associated type defaults]
21/// are stabilized.
22///
23/// [`AnyObject`]: objc2::runtime::AnyObject
24/// [associated type defaults]: https://github.com/rust-lang/rust/issues/29661
25///
26///
27/// # Safety
28///
29/// The [`Result`] type must be correct.
30///
31/// [`Result`]: Self::Result
32pub unsafe trait CopyingHelper: Message {
33 /// The immutable counterpart of the type, or `Self` if the type has no
34 /// immutable counterpart.
35 ///
36 /// The implementation for `NSString` has itself (`NSString`) here, while
37 /// `NSMutableString` instead has `NSString`.
38 type Result: Message;
39}
40
41/// A helper type for implementing [`NSMutableCopying`].
42///
43/// See [`CopyingHelper`] for the immutable variant, and more details in
44/// general. These traits are split to allow implementing
45/// `MutableCopyingHelper` only when the mutable class is available.
46///
47///
48/// # Safety
49///
50/// The [`Result`] type must be correct.
51///
52/// [`Result`]: Self::Result
53pub unsafe trait MutableCopyingHelper: Message {
54 /// The mutable counterpart of the type, or `Self` if the type has no
55 /// mutable counterpart.
56 ///
57 /// The implementation for `NSString` has `NSMutableString` here, while
58 /// `NSMutableString` has itself (`NSMutableString`).
59 type Result: Message;
60}
61
62// SAFETY: Superclasses are not in general required to implement the same
63// traits as their subclasses, but we're not dealing with normal classes and
64// arbitrary protocols, we're dealing with with immutable/mutable class
65// counterparts, and the `NSCopying`/`NSMutableCopying` protocols, which
66// _will_ be implemented on superclasses.
67unsafe impl<P: ?Sized> CopyingHelper for ProtocolObject<P> {
68 type Result = Self;
69}
70
71// SAFETY: Subclasses are required to always implement the same traits as
72// their superclasses, so a mutable subclass is required to implement the same
73// traits too.
74unsafe impl<P: ?Sized> MutableCopyingHelper for ProtocolObject<P> {
75 type Result = Self;
76}
77
78extern_protocol!(
79 /// A protocol to provide functional copies of objects.
80 ///
81 /// This is similar to Rust's [`Clone`] trait, along with sharing a few
82 /// similarities to the [`std::borrow::ToOwned`] trait with regards to the
83 /// output type.
84 ///
85 /// To allow using this in a meaningful way in Rust, we have to "enrich"
86 /// the implementation by also specifying the resulting type, see
87 /// [`CopyingHelper`] for details.
88 ///
89 /// See also [Apple's documentation][apple-doc].
90 ///
91 /// [apple-doc]: https://developer.apple.com/documentation/foundation/nscopying
92 ///
93 ///
94 /// # Examples
95 ///
96 /// Implement `NSCopying` for an externally defined class.
97 ///
98 /// ```
99 /// use objc2::extern_class;
100 /// use objc2_foundation::{CopyingHelper, NSCopying, NSObject};
101 ///
102 /// extern_class!(
103 /// #[unsafe(super(NSObject))]
104 /// # #[name = "NSData"]
105 /// struct ExampleClass;
106 /// );
107 ///
108 /// unsafe impl NSCopying for ExampleClass {}
109 ///
110 /// // Copying ExampleClass returns another ExampleClass.
111 /// unsafe impl CopyingHelper for ExampleClass {
112 /// type Result = Self;
113 /// }
114 /// ```
115 ///
116 /// Implement `NSCopying` for a custom class.
117 ///
118 /// ```
119 /// use objc2::{define_class, msg_send, AllocAnyThread, DefinedClass};
120 /// use objc2::rc::Retained;
121 /// use objc2::runtime::NSZone;
122 /// use objc2_foundation::{CopyingHelper, NSCopying, NSObject};
123 ///
124 /// define_class!(
125 /// #[unsafe(super(NSObject))]
126 /// #[name = "CustomClass"]
127 /// struct CustomClass;
128 ///
129 /// unsafe impl NSCopying for CustomClass {
130 /// #[unsafe(method_id(copyWithZone:))]
131 /// fn copyWithZone(&self, _zone: *const NSZone) -> Retained<Self> {
132 /// // Create new class, and transfer ivars
133 /// let new = Self::alloc().set_ivars(self.ivars().clone());
134 /// unsafe { msg_send![super(new), init] }
135 /// }
136 /// }
137 /// );
138 ///
139 /// // Copying CustomClass returns another CustomClass.
140 /// unsafe impl CopyingHelper for CustomClass {
141 /// type Result = Self;
142 /// }
143 /// ```
144 #[allow(clippy::missing_safety_doc)]
145 pub unsafe trait NSCopying {
146 /// Returns a new instance that's a copy of the receiver.
147 ///
148 /// The output type is the immutable counterpart of the object, which
149 /// is usually `Self`, but e.g. `NSMutableString` returns `NSString`.
150 #[unsafe(method(copy))]
151 #[unsafe(method_family = copy)]
152 #[optional]
153 fn copy(&self) -> Retained<Self::Result>
154 where
155 Self: CopyingHelper;
156
157 /// Returns a new instance that's a copy of the receiver.
158 ///
159 /// This is only used when implementing `NSCopying`, call
160 /// [`copy`][NSCopying::copy] instead.
161 ///
162 ///
163 /// # Safety
164 ///
165 /// The zone pointer must be valid or NULL.
166 #[unsafe(method(copyWithZone:))]
167 #[unsafe(method_family = copy)]
168 unsafe fn copyWithZone(&self, zone: *mut NSZone) -> Retained<Self::Result>
169 where
170 Self: CopyingHelper;
171 }
172);
173
174extern_protocol!(
175 /// A protocol to provide mutable copies of objects.
176 ///
177 /// Only classes that have an “immutable vs. mutable” distinction should
178 /// adopt this protocol. Use the [`MutableCopyingHelper`] trait to specify
179 /// the return type after copying.
180 ///
181 /// See [Apple's documentation][apple-doc] for details.
182 ///
183 /// [apple-doc]: https://developer.apple.com/documentation/foundation/nsmutablecopying
184 ///
185 ///
186 /// # Example
187 ///
188 /// Implement [`NSCopying`] and [`NSMutableCopying`] for a class pair like
189 /// `NSString` and `NSMutableString`.
190 ///
191 /// ```ignore
192 /// // Immutable copies return NSString
193 ///
194 /// unsafe impl NSCopying for NSString {}
195 /// unsafe impl CopyingHelper for NSString {
196 /// type Result = NSString;
197 /// }
198 /// unsafe impl NSCopying for NSMutableString {}
199 /// unsafe impl CopyingHelper for NSMutableString {
200 /// type Result = NSString;
201 /// }
202 ///
203 /// // Mutable copies return NSMutableString
204 ///
205 /// unsafe impl NSMutableCopying for NSString {}
206 /// unsafe impl MutableCopyingHelper for NSString {
207 /// type Result = NSMutableString;
208 /// }
209 /// unsafe impl NSMutableCopying for NSMutableString {}
210 /// unsafe impl MutableCopyingHelper for NSMutableString {
211 /// type Result = NSMutableString;
212 /// }
213 /// ```
214 #[allow(clippy::missing_safety_doc)]
215 pub unsafe trait NSMutableCopying {
216 /// Returns a new instance that's a mutable copy of the receiver.
217 ///
218 /// The output type is the mutable counterpart of the object. E.g. both
219 /// `NSString` and `NSMutableString` return `NSMutableString`.
220 #[unsafe(method(mutableCopy))]
221 #[unsafe(method_family = mutableCopy)]
222 #[optional]
223 fn mutableCopy(&self) -> Retained<Self::Result>
224 where
225 Self: MutableCopyingHelper;
226
227 /// Returns a new instance that's a mutable copy of the receiver.
228 ///
229 /// This is only used when implementing `NSMutableCopying`, call
230 /// [`mutableCopy`][NSMutableCopying::mutableCopy] instead.
231 ///
232 ///
233 /// # Safety
234 ///
235 /// The zone pointer must be valid or NULL.
236 #[unsafe(method(mutableCopyWithZone:))]
237 #[unsafe(method_family = mutableCopy)]
238 unsafe fn mutableCopyWithZone(&self, zone: *mut NSZone) -> Retained<Self::Result>
239 where
240 Self: MutableCopyingHelper;
241 }
242);