diff --git a/block2/CHANGELOG.md b/block2/CHANGELOG.md index e292d5278..70468145f 100644 --- a/block2/CHANGELOG.md +++ b/block2/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased - YYYY-MM-DD +### Added +* Added `BlockMut` to allow representing cases where you know that you're not + modifying the block concurrently, and as such may mutate the contents. + ### Fixed * **BREAKING**: Cleaned up `BlockArguments` trait, it is now sealed and a subtrait of `EncodeArguments`. diff --git a/block2/src/block.rs b/block2/src/block.rs index a5cb9566e..966419b4f 100644 --- a/block2/src/block.rs +++ b/block2/src/block.rs @@ -20,7 +20,7 @@ pub unsafe trait BlockArguments: EncodeArguments + Sized { #[doc(hidden)] unsafe fn __call_block( invoke: unsafe extern "C" fn(), - block: *mut Block, + block: *mut BlockMut, args: Self, ) -> R; } @@ -29,9 +29,9 @@ macro_rules! block_args_impl { ($($a:ident: $t:ident),*) => ( unsafe impl<$($t: Encode),*> BlockArguments for ($($t,)*) { #[inline] - unsafe fn __call_block(invoke: unsafe extern "C" fn(), block: *mut Block, ($($a,)*): Self) -> R { + unsafe fn __call_block(invoke: unsafe extern "C" fn(), block: *mut BlockMut, ($($a,)*): Self) -> R { // Very similar to `MessageArguments::__invoke` - let invoke: unsafe extern "C" fn(*mut Block $(, $t)*) -> R = unsafe { + let invoke: unsafe extern "C" fn(*mut BlockMut $(, $t)*) -> R = unsafe { mem::transmute(invoke) }; @@ -80,39 +80,80 @@ block_args_impl!( l: L ); -/// An Objective-C block that takes arguments of `A` when called and -/// returns a value of `R`. +/// A block that can be called repeatedly and may mutate state. +/// +/// This is the Objective-C equivalent of [`dyn FnMut`][FnMut]. +/// +/// Takes arguments `A` and returns `R`. #[repr(C)] -pub struct Block { +pub struct BlockMut { _inner: [u8; 0], - // We effectively store `Block_layout` + a bit more, but `Block` has to - // remain an empty type otherwise the compiler thinks we only have - // provenance over `Block_layout`. + /// We effectively store `Block_layout` + a bit more, but `Block` has to + /// remain an empty type otherwise the compiler thinks we only have + /// provenance over `Block_layout`. _layout: PhantomData, - // To get correct variance on args and return types - _p: PhantomData R>, + /// To get correct variance on args and return types + _p: PhantomData R>, } -unsafe impl RefEncode for Block { +unsafe impl RefEncode for BlockMut { const ENCODING_REF: Encoding<'static> = Encoding::Block; } -impl Block { - /// Call self with the given arguments. - /// - /// # Safety - /// - /// This invokes foreign code that the caller must verify doesn't violate - /// any of Rust's safety rules. - /// - /// For example, if this block is shared with multiple references, the - /// caller must ensure that calling it will not cause a data race. - pub unsafe fn call(&self, args: A) -> R { +impl BlockMut { + fn get_invoke(&self) -> unsafe extern "C" fn() { let ptr: *const Self = self; - let layout = unsafe { ptr.cast::().as_ref().unwrap_unchecked() }; + let ptr: *const ffi::Block_layout = ptr.cast(); + // SAFETY: `BlockMut` is `Block_layout` + extern type + let layout = unsafe { ptr.as_ref().unwrap_unchecked() }; // TODO: Is `invoke` actually ever null? - let invoke = layout.invoke.unwrap(); + layout.invoke.unwrap() + } + + /// Performs the call operation. + pub fn call_mut(&mut self, args: A) -> R { + let invoke = self.get_invoke(); + let ptr: *mut Self = self; + + unsafe { A::__call_block(invoke, ptr.cast(), args) } + } +} + +/// A block that can be called repeatedly without mutating state. +/// +/// This is the Objective-C equivalent of [`dyn Fn`][Fn]. +/// +/// Takes arguments `A` and returns `R`. +#[repr(C)] +pub struct Block { + /// Same as BlockMut, with the added ability that this may be called + /// immutably. + inner: BlockMut, +} + +unsafe impl RefEncode for Block { + const ENCODING_REF: Encoding<'static> = Encoding::Block; +} + +impl Block { + /// Performs the call operation. + pub fn call(&self, args: A) -> R { + let invoke = self.inner.get_invoke(); + let ptr: *const BlockMut = &self.inner; + + // SAFETY: `Block` is safe to call from an immutable reference + unsafe { A::__call_block(invoke, ptr as *mut BlockMut, args) } + } +} + +impl AsRef> for Block { + fn as_ref(&self) -> &BlockMut { + &self.inner + } +} - unsafe { A::__call_block(invoke, ptr as *mut Self, args) } +impl AsMut> for Block { + fn as_mut(&mut self) -> &mut BlockMut { + &mut self.inner } } diff --git a/block2/src/debug.rs b/block2/src/debug.rs index 2878ba3d6..c616954d6 100644 --- a/block2/src/debug.rs +++ b/block2/src/debug.rs @@ -4,7 +4,7 @@ use core::fmt::{Debug, DebugStruct, Error, Formatter}; use core::ptr; use std::ffi::CStr; -use crate::{ffi, Block, ConcreteBlock, GlobalBlock, RcBlock}; +use crate::{ffi, Block, BlockMut, ConcreteBlock, GlobalBlock, RcBlock}; #[derive(Clone, Copy, PartialEq, Eq)] struct Isa(*const ffi::Class); @@ -52,6 +52,16 @@ fn debug_block_layout(layout: &ffi::Block_layout, f: &mut DebugStruct<'_, '_>) { ); } +impl Debug for BlockMut { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + let mut f = f.debug_struct("BlockMut"); + let ptr: *const Self = self; + let layout = unsafe { ptr.cast::().as_ref().unwrap() }; + debug_block_layout(layout, &mut f); + f.finish_non_exhaustive() + } +} + impl Debug for Block { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { let mut f = f.debug_struct("Block"); diff --git a/block2/src/global.rs b/block2/src/global.rs index baca564e6..1f9b8f85b 100644 --- a/block2/src/global.rs +++ b/block2/src/global.rs @@ -29,7 +29,7 @@ const GLOBAL_DESCRIPTOR: ffi::Block_descriptor_header = ffi::Block_descriptor_he #[repr(C)] pub struct GlobalBlock { pub(crate) layout: ffi::Block_layout, - p: PhantomData<(A, R)>, + p: PhantomData>, } unsafe impl Sync for GlobalBlock @@ -85,7 +85,10 @@ where fn deref(&self) -> &Self::Target { let ptr: *const Self = self; let ptr: *const Block = ptr.cast(); - // TODO: SAFETY + // SAFETY: This has the same layout as `Block` + // + // A global block does not hold any data, so it is safe to call + // immutably. unsafe { ptr.as_ref().unwrap_unchecked() } } } @@ -105,7 +108,7 @@ where /// 42 /// }; /// } -/// assert_eq!(unsafe { MY_BLOCK.call(()) }, 42); +/// assert_eq!(MY_BLOCK.call(()), 42); /// ``` /// /// ``` @@ -115,7 +118,7 @@ where /// x + y /// }; /// } -/// assert_eq!(unsafe { ADDER_BLOCK.call((5, 7)) }, 12); +/// assert_eq!(ADDER_BLOCK.call((5, 7)), 12); /// ``` /// /// ``` @@ -126,7 +129,7 @@ where /// }; /// } /// let mut x = 5; -/// unsafe { MUTATING_BLOCK.call((&mut x,)) }; +/// MUTATING_BLOCK.call((&mut x,)); /// assert_eq!(x, 47); /// ``` /// @@ -206,7 +209,7 @@ mod tests { #[test] fn test_noop_block() { - unsafe { NOOP_BLOCK.call(()) }; + NOOP_BLOCK.call(()); } #[test] @@ -214,7 +217,7 @@ mod tests { global_block!(static MY_BLOCK = || -> i32 { 42 }); - assert_eq!(unsafe { MY_BLOCK.call(()) }, 42); + assert_eq!(MY_BLOCK.call(()), 42); } #[cfg(feature = "apple")] diff --git a/block2/src/lib.rs b/block2/src/lib.rs index f4ef3b3dd..9ad84ce44 100644 --- a/block2/src/lib.rs +++ b/block2/src/lib.rs @@ -103,7 +103,7 @@ mod debug; mod global; mod rc_block; -pub use block::{Block, BlockArguments}; +pub use block::{Block, BlockArguments, BlockMut}; pub use concrete_block::{ConcreteBlock, IntoConcreteBlock}; pub use global::GlobalBlock; pub use rc_block::RcBlock;