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}