block2/rc_block.rs
1use core::fmt;
2use core::mem::ManuallyDrop;
3use core::ops::Deref;
4use core::ptr::NonNull;
5
6use objc2::encode::{EncodeArguments, EncodeReturn};
7
8use crate::abi::BlockHeader;
9use crate::debug::debug_block_header;
10use crate::traits::{ManualBlockEncoding, ManualBlockEncodingExt, NoBlockEncoding, UserSpecified};
11use crate::{ffi, Block, IntoBlock, StackBlock};
12
13/// A reference-counted Objective-C block that is stored on the heap.
14///
15/// This is a smart pointer that [`Deref`]s to [`Block`].
16///
17/// The generic type `F` must be a [`dyn`] [`Fn`] that implements the
18/// [`BlockFn`] trait, just like described in [`Block`]'s documentation.
19///
20/// [`dyn`]: https://doc.rust-lang.org/std/keyword.dyn.html
21/// [`BlockFn`]: crate::BlockFn
22///
23///
24/// # Memory-layout
25///
26/// This is guaranteed to have the same size and alignment as a pointer to a
27/// block (i.e. same size as `*const Block<A, R>`).
28///
29/// Additionally, it participates in the null-pointer optimization, that is,
30/// `Option<RcBlock<A, R>>` is guaranteed to have the same size as
31/// `RcBlock<A, R>`.
32#[repr(transparent)]
33#[doc(alias = "MallocBlock")]
34#[cfg_attr(
35 feature = "unstable-coerce-pointee",
36 derive(std::marker::CoercePointee)
37)]
38pub struct RcBlock<F: ?Sized> {
39 // Covariant
40 ptr: NonNull<Block<F>>,
41}
42
43impl<F: ?Sized> RcBlock<F> {
44 /// A raw pointer to the underlying block.
45 ///
46 /// The pointer is valid for at least as long as the `RcBlock` is alive.
47 ///
48 /// This is an associated method, and must be called as
49 /// `RcBlock::as_ptr(&block)`.
50 #[inline]
51 pub fn as_ptr(this: &Self) -> *mut Block<F> {
52 this.ptr.as_ptr()
53 }
54
55 /// Consumes the `RcBlock`, passing ownership of the retain count to the
56 /// caller.
57 ///
58 /// After calling this function, the caller is responsible for releasing
59 /// the memory with [`ffi::_Block_release`] or similar.
60 ///
61 /// This is an associated method, and must be called as
62 /// `RcBlock::into_raw(block)`.
63 ///
64 ///
65 /// # Examples
66 ///
67 /// Converting a `RcBlock` to a pointer and back.
68 ///
69 /// ```
70 /// use block2::RcBlock;
71 ///
72 /// let add2 = RcBlock::new(|x: i32| -> i32 {
73 /// x + 2
74 /// });
75 /// let ptr = RcBlock::into_raw(add2);
76 /// // SAFETY: The pointer is valid, and ownership from above.
77 /// let add2 = unsafe { RcBlock::from_raw(ptr) }.unwrap();
78 /// ```
79 pub fn into_raw(this: Self) -> *mut Block<F> {
80 let this = ManuallyDrop::new(this);
81 this.ptr.as_ptr()
82 }
83
84 /// Construct an `RcBlock` from the given block pointer by taking
85 /// ownership.
86 ///
87 /// This will return `None` if the pointer is NULL.
88 ///
89 ///
90 /// # Safety
91 ///
92 /// The given pointer must point to a valid block, the parameter and
93 /// return types must be correct, and the block must have a +1 reference /
94 /// retain count from somewhere else.
95 ///
96 /// Additionally, the block must be safe to call (or, if it is not, then
97 /// you must treat every call to the block as `unsafe`).
98 #[inline]
99 pub unsafe fn from_raw(ptr: *mut Block<F>) -> Option<Self> {
100 NonNull::new(ptr).map(|ptr| Self { ptr })
101 }
102
103 /// Construct an `RcBlock` from the given block pointer.
104 ///
105 /// The block will be copied, and have its reference-count increased by
106 /// one.
107 ///
108 /// This will return `None` if the pointer is NULL, or if an allocation
109 /// failure occurred.
110 ///
111 /// See [`Block::copy`] for a safe alternative when you already know the
112 /// block pointer is valid.
113 ///
114 ///
115 /// # Safety
116 ///
117 /// The given pointer must point to a valid block, and the parameter and
118 /// return types must be correct.
119 ///
120 /// Additionally, the block must be safe to call (or, if it is not, then
121 /// you must treat every call to the block as `unsafe`).
122 #[doc(alias = "Block_copy")]
123 #[doc(alias = "_Block_copy")]
124 #[inline]
125 pub unsafe fn copy(ptr: *mut Block<F>) -> Option<Self> {
126 let ptr: *mut Block<F> = unsafe { ffi::_Block_copy(ptr.cast()) }.cast();
127 // SAFETY: We just copied the block, so the reference count is +1
128 unsafe { Self::from_raw(ptr) }
129 }
130}
131
132// TODO: Move so this appears first in the docs.
133impl<F: ?Sized> RcBlock<F> {
134 /// Construct a `RcBlock` with the given closure.
135 ///
136 /// The closure will be coped to the heap on construction.
137 ///
138 /// When the block is called, it will return the value that results from
139 /// calling the closure.
140 // Note: Unsure if this should be #[inline], but I think it may be able to
141 // benefit from not being completely so.
142 #[inline]
143 pub fn new<'f, A, R, Closure>(closure: Closure) -> Self
144 where
145 A: EncodeArguments,
146 R: EncodeReturn,
147 Closure: IntoBlock<'f, A, R, Dyn = F>,
148 {
149 Self::maybe_encoded::<_, _, _, NoBlockEncoding<A, R>>(closure)
150 }
151
152 /// Constructs a new [`RcBlock`] with the given function and encoding
153 /// information.
154 ///
155 /// See [`StackBlock::with_encoding`] as to why and how this could be
156 /// useful. The same requirements as [`Self::new`] apply here as well.
157 ///
158 /// # Example
159 ///
160 /// ```
161 /// # use core::ffi::CStr;
162 /// # use block2::{Block, ManualBlockEncoding, RcBlock};
163 /// # use objc2_foundation::NSError;
164 /// #
165 /// struct MyBlockEncoding;
166 /// // SAFETY: The encoding is correct.
167 /// unsafe impl ManualBlockEncoding for MyBlockEncoding {
168 /// type Arguments = (*mut NSError,);
169 /// type Return = i32;
170 /// const ENCODING_CSTR: &'static CStr = if cfg!(target_pointer_width = "64") {
171 /// cr#"i16@?0@"NSError"8"#
172 /// } else {
173 /// cr#"i8@?0@"NSError"4"#
174 /// };
175 /// }
176 ///
177 /// let my_block = RcBlock::with_encoding::<_, _, _, MyBlockEncoding>(|_err: *mut NSError| {
178 /// 42i32
179 /// });
180 /// assert_eq!(my_block.call((core::ptr::null_mut(),)), 42);
181 /// ```
182 #[inline]
183 pub fn with_encoding<'f, A, R, Closure, E>(closure: Closure) -> Self
184 where
185 A: EncodeArguments,
186 R: EncodeReturn,
187 Closure: IntoBlock<'f, A, R, Dyn = F>,
188 E: ManualBlockEncoding<Arguments = A, Return = R>,
189 {
190 Self::maybe_encoded::<_, _, _, UserSpecified<E>>(closure)
191 }
192
193 fn maybe_encoded<'f, A, R, Closure, E>(closure: Closure) -> Self
194 where
195 A: EncodeArguments,
196 R: EncodeReturn,
197 Closure: IntoBlock<'f, A, R, Dyn = F>,
198 E: ManualBlockEncodingExt<Arguments = A, Return = R>,
199 {
200 // SAFETY: The stack block is copied once below.
201 //
202 // Note: We could theoretically use `_NSConcreteMallocBlock`, and use
203 // `malloc` ourselves to put the block on the heap, but that symbol is
204 // not part of the public ABI, and may break in the future.
205 //
206 // Clang doesn't do this optimization either.
207 // <https://github.com/llvm/llvm-project/blob/llvmorg-17.0.6/clang/lib/CodeGen/CGBlocks.cpp#L281-L284>
208 let block = unsafe { StackBlock::new_no_clone::<E>(closure) };
209
210 // Transfer ownership from the stack to the heap.
211 let mut block = ManuallyDrop::new(block);
212 let ptr: *mut StackBlock<'f, A, R, Closure> = &mut *block;
213 let ptr: *mut Block<F> = ptr.cast();
214 // SAFETY: The block will be moved to the heap, and we forget the
215 // original block because the heap block will drop in our dispose
216 // helper.
217 unsafe { Self::copy(ptr) }.unwrap_or_else(|| rc_new_fail())
218 }
219}
220
221impl<F: ?Sized> Clone for RcBlock<F> {
222 /// Increase the reference-count of the block.
223 #[doc(alias = "Block_copy")]
224 #[doc(alias = "_Block_copy")]
225 #[inline]
226 fn clone(&self) -> Self {
227 // SAFETY: The block pointer is valid, and its safety invariant is
228 // upheld, since the only way to get an `RcBlock` in the first place
229 // is through unsafe functions that requires these preconditions to be
230 // upheld.
231 unsafe { Self::copy(self.ptr.as_ptr()) }.unwrap_or_else(|| rc_clone_fail())
232 }
233}
234
235// Intentionally not `#[track_caller]`, to keep the code-size smaller (as this
236// error is very unlikely).
237fn rc_new_fail() -> ! {
238 // This likely means the system is out of memory.
239 panic!("failed creating RcBlock")
240}
241
242// Intentionally not `#[track_caller]`, see above.
243pub(crate) fn block_copy_fail() -> ! {
244 // This likely means the system is out of memory.
245 panic!("failed copying Block")
246}
247
248// Intentionally not `#[track_caller]`, see above.
249fn rc_clone_fail() -> ! {
250 unreachable!("cloning a RcBlock bumps the reference count, which should be infallible")
251}
252
253impl<F: ?Sized> Deref for RcBlock<F> {
254 type Target = Block<F>;
255
256 #[inline]
257 fn deref(&self) -> &Block<F> {
258 // SAFETY: The pointer is valid, as ensured by creation methods, and
259 // will be so for as long as the `RcBlock` is, since that holds +1
260 // reference count.
261 unsafe { self.ptr.as_ref() }
262 }
263}
264
265impl<F: ?Sized> Drop for RcBlock<F> {
266 /// Release the block, decreasing the reference-count by 1.
267 ///
268 /// The `Drop` method of the underlying closure will be called once the
269 /// reference-count reaches zero.
270 #[doc(alias = "Block_release")]
271 #[doc(alias = "_Block_release")]
272 #[inline]
273 fn drop(&mut self) {
274 // SAFETY: The pointer has +1 reference count, as ensured by creation
275 // methods.
276 unsafe { ffi::_Block_release(self.ptr.as_ptr().cast()) };
277 }
278}
279
280impl<F: ?Sized> fmt::Debug for RcBlock<F> {
281 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
282 let mut f = f.debug_struct("RcBlock");
283 let header = unsafe { self.ptr.cast::<BlockHeader>().as_ref() };
284 debug_block_header(header, &mut f);
285 f.finish_non_exhaustive()
286 }
287}
288
289#[cfg(test)]
290mod tests {
291 use alloc::rc::Rc;
292 use core::cell::OnceCell;
293
294 use super::*;
295
296 #[test]
297 fn return_rc_block() {
298 fn get_adder(x: i32) -> RcBlock<dyn Fn(i32) -> i32> {
299 RcBlock::new(move |y| y + x)
300 }
301
302 let add2 = get_adder(2);
303 assert_eq!(add2.call((5,)), 7);
304 assert_eq!(add2.call((-1,)), 1);
305 }
306
307 #[test]
308 fn rc_block_with_precisely_described_lifetimes() {
309 fn args<'a, 'b>(
310 f: impl Fn(&'a i32, &'b i32) + 'static,
311 ) -> RcBlock<dyn Fn(&'a i32, &'b i32) + 'static> {
312 RcBlock::new(f)
313 }
314
315 fn args_return<'a, 'b>(
316 f: impl Fn(&'a i32) -> &'b i32 + 'static,
317 ) -> RcBlock<dyn Fn(&'a i32) -> &'b i32 + 'static> {
318 RcBlock::new(f)
319 }
320
321 fn args_entire<'a, 'b>(f: impl Fn(&'a i32) + 'b) -> RcBlock<dyn Fn(&'a i32) + 'b> {
322 RcBlock::new(f)
323 }
324
325 fn return_entire<'a, 'b>(
326 f: impl Fn() -> &'a i32 + 'b,
327 ) -> RcBlock<dyn Fn() -> &'a i32 + 'b> {
328 RcBlock::new(f)
329 }
330
331 let _ = args(|_, _| {});
332 let _ = args_return(|x| x);
333 let _ = args_entire(|_| {});
334 let _ = return_entire(|| &5);
335 }
336
337 #[allow(dead_code)]
338 fn covariant<'f>(b: RcBlock<dyn Fn() + 'static>) -> RcBlock<dyn Fn() + 'f> {
339 b
340 }
341
342 #[test]
343 fn allow_re_entrancy() {
344 #[allow(clippy::type_complexity)]
345 let block: Rc<OnceCell<RcBlock<dyn Fn(u32) -> u32>>> = Rc::new(OnceCell::new());
346
347 let captured_block = block.clone();
348 let fibonacci = move |n| {
349 let captured_fibonacci = captured_block.get().unwrap();
350 match n {
351 0 => 0,
352 1 => 1,
353 n => captured_fibonacci.call((n - 1,)) + captured_fibonacci.call((n - 2,)),
354 }
355 };
356
357 let block = block.get_or_init(|| RcBlock::new(fibonacci));
358
359 assert_eq!(block.call((0,)), 0);
360 assert_eq!(block.call((1,)), 1);
361 assert_eq!(block.call((6,)), 8);
362 assert_eq!(block.call((10,)), 55);
363 assert_eq!(block.call((19,)), 4181);
364 }
365}