block2/
global.rs

1use core::ffi::c_ulong;
2use core::fmt;
3use core::marker::PhantomData;
4use core::mem;
5use core::mem::MaybeUninit;
6use core::ops::Deref;
7use core::ptr::{self, NonNull};
8
9use crate::abi::{BlockDescriptor, BlockDescriptorPtr, BlockFlags, BlockHeader};
10use crate::debug::debug_block_header;
11use crate::{Block, BlockFn};
12
13// TODO: Should this be a static to help the compiler deduplicating them?
14const GLOBAL_DESCRIPTOR: BlockDescriptor = BlockDescriptor {
15    reserved: 0,
16    size: mem::size_of::<BlockHeader>() as c_ulong,
17};
18
19/// A global Objective-C block that does not capture an environment.
20///
21/// This is a smart pointer that [`Deref`]s to [`Block`].
22///
23/// It can created and stored in static memory using the [`global_block!`]
24/// macro.
25///
26/// [`global_block!`]: crate::global_block
27#[repr(C)]
28pub struct GlobalBlock<F: ?Sized> {
29    header: BlockHeader,
30    // We don't store a function pointer, instead it is placed inside the
31    // invoke function.
32    f: PhantomData<F>,
33}
34
35// TODO: Add `Send + Sync` bounds once the block itself supports that.
36unsafe impl<F: ?Sized + BlockFn> Sync for GlobalBlock<F> {}
37unsafe impl<F: ?Sized + BlockFn> Send for GlobalBlock<F> {}
38
39// Note: We can't put correct bounds on A and R because we have a const fn,
40// and that's not allowed yet in our MSRV.
41//
42// Fortunately, we don't need them, since they're present on `Sync`, so
43// constructing the static in `global_block!` with an invalid `GlobalBlock`
44// triggers an error.
45impl<F: ?Sized> GlobalBlock<F> {
46    // TODO: Use new ABI with BLOCK_HAS_SIGNATURE
47    const FLAGS: BlockFlags = BlockFlags::BLOCK_IS_GLOBAL.union(BlockFlags::BLOCK_USE_STRET);
48
49    #[doc(hidden)]
50    #[allow(clippy::declare_interior_mutable_const)]
51    pub const __DEFAULT_HEADER: BlockHeader = BlockHeader {
52        // Populated in `global_block!`
53        isa: ptr::null_mut(),
54        flags: Self::FLAGS,
55        reserved: MaybeUninit::new(0),
56        // Populated in `global_block!`
57        invoke: None,
58        descriptor: BlockDescriptorPtr {
59            basic: &GLOBAL_DESCRIPTOR,
60        },
61    };
62
63    /// Use the [`global_block`] macro instead.
64    #[doc(hidden)]
65    #[inline]
66    pub const unsafe fn from_header(header: BlockHeader) -> Self {
67        Self {
68            header,
69            f: PhantomData,
70        }
71    }
72
73    // TODO: Add some constructor for when `F: Copy`.
74}
75
76impl<F: ?Sized + BlockFn> Deref for GlobalBlock<F> {
77    type Target = Block<F>;
78
79    #[inline]
80    fn deref(&self) -> &Self::Target {
81        let ptr: NonNull<Self> = NonNull::from(self);
82        let ptr: NonNull<Block<F>> = ptr.cast();
83        // SAFETY: This has the same layout as `Block`
84        //
85        // A global block does not hold any data, so it is safe to call
86        // immutably.
87        unsafe { ptr.as_ref() }
88    }
89}
90
91impl<F: ?Sized> fmt::Debug for GlobalBlock<F> {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        let mut f = f.debug_struct("GlobalBlock");
94        debug_block_header(&self.header, &mut f);
95        f.finish_non_exhaustive()
96    }
97}
98
99/// Construct a static [`GlobalBlock`].
100///
101/// The syntax is similar to a static closure (except that all types have to
102/// be specified). Note that the block cannot capture its environment, its
103/// parameter types must be [`EncodeArgument`] and the return type must be
104/// [`EncodeReturn`].
105///
106/// [`EncodeArgument`]: objc2::encode::EncodeArgument
107/// [`EncodeReturn`]: objc2::encode::EncodeReturn
108///
109/// # Examples
110///
111/// ```
112/// use block2::global_block;
113/// global_block! {
114///     static MY_BLOCK = || -> i32 {
115///         42
116///     };
117/// }
118/// assert_eq!(MY_BLOCK.call(()), 42);
119/// ```
120///
121/// ```
122/// use block2::global_block;
123/// global_block! {
124///     static ADDER_BLOCK = |x: i32, y: i32| -> i32 {
125///         x + y
126///     };
127/// }
128/// assert_eq!(ADDER_BLOCK.call((5, 7)), 12);
129/// ```
130///
131/// The following does not compile because [`Box`] is not [`EncodeReturn`]:
132///
133/// ```compile_fail,E0277
134/// use block2::global_block;
135/// global_block! {
136///     pub static BLOCK = |b: Box<i32>| {};
137/// }
138/// ```
139///
140/// This also doesn't work (yet), as blocks are overly restrictive about the
141/// lifetimes involved.
142///
143/// ```compile_fail
144/// use block2::global_block;
145/// global_block! {
146///     pub static BLOCK_WITH_LIFETIME = |x: &i32| -> i32 {
147///         *x + 42
148///     };
149/// }
150/// let x = 5;
151/// let res = BLOCK_WITH_LIFETIME.call((&x,));
152/// assert_eq!(res, 47);
153/// ```
154///
155/// There is also no way to get a block function that's generic over its
156/// parameters. One could imagine the following syntax would work, but it
157/// can't due to implementation limitations:
158///
159/// ```compile_fail
160/// use block2::global_block;
161/// global_block! {
162///     pub static BLOCK<T: Encode> = |b: T| {};
163/// }
164/// ```
165///
166/// [`Box`]: alloc::boxed::Box
167#[macro_export]
168macro_rules! global_block {
169    // `||` is parsed as one token
170    (
171        $(#[$m:meta])*
172        $vis:vis static $name:ident = || $(-> $r:ty)? $body:block $(;)?
173    ) => {
174        $crate::global_block!(
175            $(#[$m])*
176            $vis static $name = |,| $(-> $r)? $body
177        );
178    };
179    (
180        $(#[$m:meta])*
181        $vis:vis static $name:ident = |$($a:ident: $t:ty),* $(,)?| $(-> $r:ty)? $body:block $(;)?
182    ) => {
183        $(#[$m])*
184        #[allow(unused_unsafe)]
185        $vis static $name: $crate::GlobalBlock<dyn Fn($($t),*) $(-> $r)? + 'static> = unsafe {
186            let mut header = $crate::GlobalBlock::<dyn Fn($($t),*) $(-> $r)? + 'static>::__DEFAULT_HEADER;
187            header.isa = ::core::ptr::addr_of!($crate::ffi::_NSConcreteGlobalBlock);
188            header.invoke = ::core::option::Option::Some({
189                unsafe extern "C-unwind" fn inner(
190                    _: *mut $crate::GlobalBlock<dyn Fn($($t),*) $(-> $r)? + 'static>,
191                    $($a: $t),*
192                ) $(-> $r)? {
193                    $body
194                }
195
196                // TODO: SAFETY
197                ::core::mem::transmute::<
198                    unsafe extern "C-unwind" fn(*mut $crate::GlobalBlock<dyn Fn($($t),*) $(-> $r)? + 'static>, $($a: $t),*) $(-> $r)?,
199                    unsafe extern "C-unwind" fn(),
200                >(inner)
201            });
202            $crate::GlobalBlock::from_header(header)
203        };
204    };
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210    use alloc::format;
211
212    global_block! {
213        /// Test comments and visibility
214        pub(super) static NOOP_BLOCK = || {};
215    }
216
217    global_block! {
218        /// Multiple parameters + trailing comma
219        #[allow(unused)]
220        static BLOCK = |x: i32, y: i32, z: i32, w: i32,| -> i32 {
221            x + y + z + w
222        };
223    }
224
225    #[test]
226    fn test_noop_block() {
227        NOOP_BLOCK.call(());
228    }
229
230    #[test]
231    fn test_defined_in_function() {
232        global_block!(static MY_BLOCK = || -> i32 {
233            42
234        });
235        assert_eq!(MY_BLOCK.call(()), 42);
236    }
237
238    #[cfg(target_vendor = "apple")]
239    const DEBUG_BLOCKFLAGS: &str = r#"BlockFlags {
240        value: "00110000000000000000000000000000",
241        deallocating: false,
242        inline_layout_string: false,
243        small_descriptor: false,
244        is_noescape: false,
245        needs_free: false,
246        has_copy_dispose: false,
247        has_ctor: false,
248        is_gc: false,
249        is_global: true,
250        use_stret: true,
251        has_signature: false,
252        has_extended_layout: false,
253        over_referenced: false,
254        reference_count: 0,
255        ..
256    }"#;
257
258    #[cfg(not(target_vendor = "apple"))]
259    const DEBUG_BLOCKFLAGS: &str = r#"BlockFlags {
260        value: "00110000000000000000000000000000",
261        has_copy_dispose: false,
262        has_ctor: false,
263        is_global: true,
264        use_stret: true,
265        has_signature: false,
266        over_referenced: false,
267        reference_count: 0,
268        ..
269    }"#;
270
271    #[test]
272    fn test_debug() {
273        let invoke = NOOP_BLOCK.header.invoke.unwrap();
274        let size = mem::size_of::<BlockHeader>();
275        let maybeuninit = <MaybeUninit<i32>>::uninit();
276        let expected = format!(
277            "GlobalBlock {{
278    isa: _NSConcreteGlobalBlock,
279    flags: {DEBUG_BLOCKFLAGS},
280    reserved: {maybeuninit:?},
281    invoke: Some(
282        {invoke:#?},
283    ),
284    descriptor: BlockDescriptor {{
285        reserved: 0,
286        size: {size},
287    }},
288    ..
289}}"
290        );
291        assert_eq!(format!("{NOOP_BLOCK:#?}"), expected);
292    }
293
294    #[allow(dead_code)]
295    fn covariant<'f>(b: GlobalBlock<dyn Fn() + 'static>) -> GlobalBlock<dyn Fn() + 'f> {
296        b
297    }
298}