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