use core::fmt; use core::mem::ManuallyDrop; use core::ops::Deref; use core::ptr::NonNull; use objc2::encode::{EncodeArguments, EncodeReturn}; use crate::abi::BlockHeader; use crate::debug::debug_block_header; use crate::{ffi, Block, IntoBlock, StackBlock}; /// A reference-counted Objective-C block that is stored on the heap. /// /// This is a smart pointer that [`Deref`]s to [`Block`]. /// /// The generic type `F` must be a [`dyn`] [`Fn`] that implements the /// [`BlockFn`] trait, just like described in [`Block`]'s documentation. /// /// [`dyn`]: https://doc.rust-lang.org/std/keyword.dyn.html /// [`BlockFn`]: crate::BlockFn /// /// /// # Memory-layout /// /// This is guaranteed to have the same size and alignment as a pointer to a /// block (i.e. same size as `*const Block`). /// /// Additionally, it participates in the null-pointer optimization, that is, /// `Option>` is guaranteed to have the same size as /// `RcBlock`. #[doc(alias = "MallocBlock")] pub struct RcBlock { // Covariant ptr: NonNull>, } impl RcBlock { /// Construct an `RcBlock` from the given block pointer by taking /// ownership. /// /// This will return `None` if the pointer is NULL. /// /// /// # Safety /// /// The given pointer must point to a valid block, the parameter and /// return types must be correct, and the block must have a +1 reference / /// retain count from somewhere else. /// /// Additionally, the block must be safe to call (or, if it is not, then /// you must treat every call to the block as `unsafe`). #[inline] pub unsafe fn from_raw(ptr: *mut Block) -> Option { NonNull::new(ptr).map(|ptr| Self { ptr }) } /// Construct an `RcBlock` from the given block pointer. /// /// The block will be copied, and have its reference-count increased by /// one. /// /// This will return `None` if the pointer is NULL, or if an allocation /// failure occurred. /// /// See [`Block::copy`] for a safe alternative when you already know the /// block pointer is valid. /// /// /// # Safety /// /// The given pointer must point to a valid block, and the parameter and /// return types must be correct. /// /// Additionally, the block must be safe to call (or, if it is not, then /// you must treat every call to the block as `unsafe`). #[doc(alias = "Block_copy")] #[doc(alias = "_Block_copy")] #[inline] pub unsafe fn copy(ptr: *mut Block) -> Option { let ptr: *mut Block = unsafe { ffi::_Block_copy(ptr.cast()) }.cast(); // SAFETY: We just copied the block, so the reference count is +1 unsafe { Self::from_raw(ptr) } } } // TODO: Move so this appears first in the docs. impl RcBlock { /// Construct a `RcBlock` with the given closure. /// /// The closure will be coped to the heap on construction. /// /// When the block is called, it will return the value that results from /// calling the closure. // // Note: Unsure if this should be #[inline], but I think it may be able to // benefit from not being so. pub fn new<'f, A, R, Closure>(closure: Closure) -> Self where A: EncodeArguments, R: EncodeReturn, Closure: IntoBlock<'f, A, R, Dyn = F>, { // SAFETY: The stack block is copied once below. // // Note: We could theoretically use `_NSConcreteMallocBlock`, and use // `malloc` ourselves to put the block on the heap, but that symbol is // not part of the public ABI, and may break in the future. // // Clang doesn't do this optimization either. // let block = unsafe { StackBlock::new_no_clone(closure) }; // Transfer ownership from the stack to the heap. let mut block = ManuallyDrop::new(block); let ptr: *mut StackBlock<'f, A, R, Closure> = &mut *block; let ptr: *mut Block = ptr.cast(); // SAFETY: The block will be moved to the heap, and we forget the // original block because the heap block will drop in our dispose // helper. unsafe { Self::copy(ptr) }.unwrap_or_else(|| rc_new_fail()) } } impl Clone for RcBlock { /// Increase the reference-count of the block. #[doc(alias = "Block_copy")] #[doc(alias = "_Block_copy")] #[inline] fn clone(&self) -> Self { // SAFETY: The block pointer is valid, and its safety invariant is // upheld, since the only way to get an `RcBlock` in the first place // is through unsafe functions that requires these preconditions to be // upheld. unsafe { Self::copy(self.ptr.as_ptr()) }.unwrap_or_else(|| rc_clone_fail()) } } // Intentionally not `#[track_caller]`, to keep the code-size smaller (as this // error is very unlikely). fn rc_new_fail() -> ! { // This likely means the system is out of memory. panic!("failed creating RcBlock") } // Intentionally not `#[track_caller]`, see above. pub(crate) fn block_copy_fail() -> ! { // This likely means the system is out of memory. panic!("failed copying Block") } // Intentionally not `#[track_caller]`, see above. fn rc_clone_fail() -> ! { unreachable!("cloning a RcBlock bumps the reference count, which should be infallible") } impl Deref for RcBlock { type Target = Block; #[inline] fn deref(&self) -> &Block { // SAFETY: The pointer is valid, as ensured by creation methods, and // will be so for as long as the `RcBlock` is, since that holds +1 // reference count. unsafe { self.ptr.as_ref() } } } impl Drop for RcBlock { /// Release the block, decreasing the reference-count by 1. /// /// The `Drop` method of the underlying closure will be called once the /// reference-count reaches zero. #[doc(alias = "Block_release")] #[doc(alias = "_Block_release")] #[inline] fn drop(&mut self) { // SAFETY: The pointer has +1 reference count, as ensured by creation // methods. unsafe { ffi::_Block_release(self.ptr.as_ptr().cast()) }; } } impl fmt::Debug for RcBlock { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut f = f.debug_struct("RcBlock"); let header = unsafe { self.ptr.cast::().as_ref() }; debug_block_header(header, &mut f); f.finish_non_exhaustive() } } #[cfg(test)] mod tests { use alloc::rc::Rc; use core::cell::OnceCell; use super::*; #[test] fn return_rc_block() { fn get_adder(x: i32) -> RcBlock i32> { RcBlock::new(move |y| y + x) } let add2 = get_adder(2); assert_eq!(add2.call((5,)), 7); assert_eq!(add2.call((-1,)), 1); } #[test] fn rc_block_with_precisely_described_lifetimes() { fn args<'a, 'b>( f: impl Fn(&'a i32, &'b i32) + 'static, ) -> RcBlock { RcBlock::new(f) } fn args_return<'a, 'b>( f: impl Fn(&'a i32) -> &'b i32 + 'static, ) -> RcBlock &'b i32 + 'static> { RcBlock::new(f) } fn args_entire<'a, 'b>(f: impl Fn(&'a i32) + 'b) -> RcBlock { RcBlock::new(f) } fn return_entire<'a, 'b>( f: impl Fn() -> &'a i32 + 'b, ) -> RcBlock &'a i32 + 'b> { RcBlock::new(f) } let _ = args(|_, _| {}); let _ = args_return(|x| x); let _ = args_entire(|_| {}); let _ = return_entire(|| &5); } #[allow(dead_code)] fn covariant<'f>(b: RcBlock) -> RcBlock { b } #[test] fn allow_re_entrancy() { #[allow(clippy::type_complexity)] let block: Rc u32>>> = Rc::new(OnceCell::new()); let captured_block = block.clone(); let fibonacci = move |n| { let captured_fibonacci = captured_block.get().unwrap(); match n { 0 => 0, 1 => 1, n => captured_fibonacci.call((n - 1,)) + captured_fibonacci.call((n - 2,)), } }; let block = block.get_or_init(|| RcBlock::new(fibonacci)); assert_eq!(block.call((0,)), 0); assert_eq!(block.call((1,)), 1); assert_eq!(block.call((6,)), 8); assert_eq!(block.call((10,)), 55); assert_eq!(block.call((19,)), 4181); } }