block2/
block.rs

1use core::fmt;
2use core::marker::PhantomData;
3use core::ptr::NonNull;
4
5use objc2::encode::{Encoding, RefEncode};
6
7use crate::abi::BlockHeader;
8use crate::debug::debug_block_header;
9use crate::rc_block::block_copy_fail;
10use crate::{BlockFn, RcBlock};
11
12/// An opaque type that holds an Objective-C block.
13///
14/// The generic type `F` must be a [`dyn`] [`Fn`] that implements
15/// the [`BlockFn`] trait (which means parameter and return types must be
16/// "encodable"), and describes the parameter and return types of the block.
17///
18/// For example, you may have the type `Block<dyn Fn(u8, u8) -> i32>`, and
19/// that would be a `'static` block that takes two `u8`s, and returns an
20/// `i32`.
21///
22/// If you want the block to carry a lifetime, use `Block<dyn Fn() + 'a>`,
23/// just like you'd usually do with `dyn Fn`.
24///
25/// [`dyn`]: https://doc.rust-lang.org/std/keyword.dyn.html
26///
27///
28/// # Memory layout
29///
30/// This is intended to be an `extern type`, and as such the memory layout of
31/// this type is _not_ guaranteed. That said, **pointers** to this type are
32/// always thin, and match that of Objective-C blocks. So the layout of e.g.
33/// `&Block<dyn Fn(...) -> ... + '_>` is defined, and guaranteed to be
34/// pointer-sized and ABI-compatible with a block pointer.
35///
36///
37/// # Safety invariant
38///
39/// Calling this potentially invokes foreign code, so you must verify, when
40/// creating a reference to this, or returning it from an external API, that
41/// it doesn't violate any of Rust's safety rules.
42///
43/// In particular, blocks are sharable with multiple references (see e.g.
44/// [`Block::copy`]), so the caller must ensure that calling it can never
45/// cause a data race. This usually means you'll have to use some form of
46/// interior mutability, if you need to mutate something from inside a block.
47//
48// TODO: Potentially restrict to `F: BlockFn`, for better error messages?
49#[repr(C)]
50pub struct Block<F: ?Sized> {
51    _inner: [u8; 0],
52    /// We store `BlockHeader` + the closure captures, but `Block` has to
53    /// remain an empty type because we don't know the size of the closure,
54    /// and otherwise the compiler would think we only have provenance over
55    /// `BlockHeader`.
56    ///
57    /// This is possible to improve once we have extern types.
58    _header: PhantomData<BlockHeader>,
59    _p: PhantomData<F>,
60}
61
62// SAFETY: Pointers to `Block` is an Objective-C block.
63// This is only valid when `F: BlockFn`, as that bounds the parameters and
64// return type to be encodable too.
65unsafe impl<F: ?Sized + BlockFn> RefEncode for Block<F> {
66    const ENCODING_REF: Encoding = Encoding::Block;
67}
68
69impl<F: ?Sized> Block<F> {
70    fn header(&self) -> &BlockHeader {
71        let ptr: NonNull<Self> = NonNull::from(self);
72        let ptr: NonNull<BlockHeader> = ptr.cast();
73        // SAFETY: `Block` is `BlockHeader` + closure
74        unsafe { ptr.as_ref() }
75    }
76
77    /// Copy the block onto the heap as an [`RcBlock`].
78    ///
79    /// The behaviour of this function depends on whether the block is from a
80    /// [`RcBlock`] or a [`StackBlock`]. In the former case, it will bump the
81    /// reference-count (just as-if you'd `Clone`'d the `RcBlock`), in the
82    /// latter case it will construct a new `RcBlock` from the `StackBlock`.
83    ///
84    /// This distinction should not matter, except for micro-optimizations.
85    ///
86    /// [`StackBlock`]: crate::StackBlock
87    #[doc(alias = "Block_copy")]
88    #[doc(alias = "_Block_copy")]
89    #[inline]
90    pub fn copy(&self) -> RcBlock<F> {
91        let ptr: *const Self = self;
92        let ptr: *mut Block<F> = ptr as *mut _;
93        // SAFETY: The lifetime of the block is extended from `&self` to that
94        // of the `RcBlock`, which is fine, because the lifetime of the
95        // contained closure `F` is still carried along to the `RcBlock`.
96        unsafe { RcBlock::copy(ptr) }.unwrap_or_else(|| block_copy_fail())
97    }
98
99    /// Call the block.
100    ///
101    /// The arguments must be passed as a tuple. The return is the output of
102    /// the block.
103    #[doc(alias = "invoke")]
104    pub fn call(&self, args: F::Args) -> F::Output
105    where
106        F: BlockFn,
107    {
108        // TODO: Is `invoke` actually ever null?
109        let invoke = self.header().invoke.unwrap_or_else(|| unreachable!());
110
111        let ptr: NonNull<Self> = NonNull::from(self);
112        let ptr: *mut Self = ptr.as_ptr();
113
114        // SAFETY: The closure is an `Fn`, and as such is safe to call from an
115        // immutable reference.
116        unsafe { F::__call_block(invoke, ptr, args) }
117    }
118}
119
120impl<F: ?Sized> fmt::Debug for Block<F> {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        let mut f = f.debug_struct("Block");
123        debug_block_header(self.header(), &mut f);
124        f.finish_non_exhaustive()
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use core::cell::Cell;
131    use core::sync::atomic::{AtomicUsize, Ordering};
132
133    use super::*;
134
135    /// Test that the way you specify lifetimes are as documented in the
136    /// reference.
137    /// <https://doc.rust-lang.org/nightly/reference/lifetime-elision.html#default-trait-object-lifetimes>
138    #[test]
139    fn test_rust_dyn_lifetime_semantics() {
140        fn takes_static(block: &Block<dyn Fn() + 'static>) {
141            block.call(());
142        }
143
144        fn takes_elided(block: &Block<dyn Fn() + '_>) {
145            block.call(());
146        }
147
148        fn takes_unspecified(block: &Block<dyn Fn()>) {
149            block.call(());
150        }
151
152        // Static lifetime
153        static MY_STATIC: AtomicUsize = AtomicUsize::new(0);
154        MY_STATIC.store(0, Ordering::Relaxed);
155        let static_lifetime: RcBlock<dyn Fn() + 'static> = RcBlock::new(|| {
156            MY_STATIC.fetch_add(1, Ordering::Relaxed);
157        });
158        takes_static(&static_lifetime);
159        takes_elided(&static_lifetime);
160        takes_unspecified(&static_lifetime);
161        assert_eq!(MY_STATIC.load(Ordering::Relaxed), 3);
162
163        // Lifetime declared with `'_`
164        let captured = Cell::new(0);
165        let elided_lifetime: RcBlock<dyn Fn() + '_> = RcBlock::new(|| {
166            captured.set(captured.get() + 1);
167        });
168        // takes_static(&elided_lifetime); // Compile error
169        takes_elided(&elided_lifetime);
170        // takes_unspecified(&elided_lifetime); // Compile error
171        assert_eq!(captured.get(), 1);
172
173        // Lifetime kept unspecified
174        let captured = Cell::new(0);
175        let unspecified_lifetime: RcBlock<dyn Fn()> = RcBlock::new(|| {
176            captured.set(captured.get() + 1);
177        });
178        // takes_static(&unspecified_lifetime); // Compile error
179        takes_elided(&unspecified_lifetime);
180        // takes_unspecified(&unspecified_lifetime); // Compile error
181        assert_eq!(captured.get(), 1);
182    }
183
184    #[allow(dead_code)]
185    fn unspecified_in_fn_is_static(block: &Block<dyn Fn()>) -> &Block<dyn Fn() + 'static> {
186        block
187    }
188
189    #[allow(dead_code)]
190    fn lending_block<'b>(block: &Block<dyn Fn() -> &'b i32 + 'b>) {
191        let _ = *block.call(());
192    }
193
194    #[allow(dead_code)]
195    fn takes_lifetime(_: &Block<dyn Fn(&i32) -> &i32>) {
196        // Not actually callable yet
197    }
198
199    #[allow(dead_code)]
200    fn covariant<'b, 'f>(b: &'b Block<dyn Fn() + 'static>) -> &'b Block<dyn Fn() + 'f> {
201        b
202    }
203}