Skip to content

Commit ceebf7d

Browse files
committed
Add global_block! macro
1 parent 121e34c commit ceebf7d

File tree

4 files changed

+206
-1
lines changed

4 files changed

+206
-1
lines changed

block2/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

77
## Unreleased - YYYY-MM-DD
88

9+
### Added
10+
* `GlobalBlock` and corresponding `global_block!` macro, allowing statically
11+
creating blocks that don't reference their environment.
12+
913

1014
## 0.2.0-alpha.1 - 2021-11-22
1115

block2/src/global.rs

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
use core::marker::PhantomData;
2+
use core::mem;
3+
use core::ops::Deref;
4+
use std::os::raw::c_ulong;
5+
6+
use objc2_encode::{Encode, EncodeArguments};
7+
8+
use super::{ffi, Block};
9+
use crate::BlockArguments;
10+
11+
#[doc(hidden)]
12+
pub const __GLOBAL_DESCRIPTOR: ffi::Block_descriptor_header = ffi::Block_descriptor_header {
13+
reserved: 0,
14+
size: mem::size_of::<ffi::Block_layout>() as c_ulong,
15+
};
16+
17+
/// An Objective-C block that does not capture it's environment.
18+
///
19+
/// This is effectively just a glorified function pointer, and can created and
20+
/// stored in static memory using the [`global_block`][`global_block!`] macro.
21+
///
22+
/// If [`ConcreteBlock`] is the [`Fn`]-block equivalent, this is likewise the
23+
/// [`fn`]-block equivalent.
24+
#[repr(C)]
25+
pub struct GlobalBlock<A, R = ()> {
26+
layout: ffi::Block_layout,
27+
p: PhantomData<(A, R)>,
28+
}
29+
30+
unsafe impl<A, R> Sync for GlobalBlock<A, R>
31+
where
32+
A: BlockArguments + EncodeArguments,
33+
R: Encode,
34+
{
35+
}
36+
unsafe impl<A, R> Send for GlobalBlock<A, R>
37+
where
38+
A: BlockArguments + EncodeArguments,
39+
R: Encode,
40+
{
41+
}
42+
43+
// Note: We can't put correct bounds on A and R because we have a const fn!
44+
//
45+
// Fortunately, we don't need them, since they're present on `Sync`, so
46+
// constructing the static in `global_block!` with an invalid `GlobalBlock`
47+
// triggers an error.
48+
impl<A, R> GlobalBlock<A, R> {
49+
/// Use the [`global_block`] macro instead.
50+
#[doc(hidden)]
51+
pub const unsafe fn from_layout(layout: ffi::Block_layout) -> Self {
52+
Self {
53+
layout,
54+
p: PhantomData,
55+
}
56+
}
57+
}
58+
59+
impl<A, R> Deref for GlobalBlock<A, R>
60+
where
61+
A: BlockArguments + EncodeArguments,
62+
R: Encode,
63+
{
64+
type Target = Block<A, R>;
65+
66+
fn deref(&self) -> &Block<A, R> {
67+
// TODO: SAFETY
68+
unsafe { &*(self as *const Self as *const Block<A, R>) }
69+
}
70+
}
71+
72+
/// Construct a static [`GlobalBlock`].
73+
///
74+
/// The syntax is similar to a static closure. Note that the block cannot
75+
/// capture it's environment, and it's argument types and return type must be
76+
/// [`Encode`].
77+
///
78+
/// # Examples
79+
///
80+
/// ```
81+
/// use block2::global_block;
82+
/// global_block! {
83+
/// static MY_BLOCK = || -> i32 {
84+
/// 42
85+
/// }
86+
/// };
87+
/// assert_eq!(unsafe { MY_BLOCK.call(()) }, 42);
88+
/// ```
89+
///
90+
/// ```
91+
/// use block2::global_block;
92+
/// global_block! {
93+
/// static ADDER_BLOCK = |x: i32, y: i32,| -> i32 {
94+
/// x + y
95+
/// }
96+
/// };
97+
/// assert_eq!(unsafe { ADDER_BLOCK.call((5, 7)) }, 12);
98+
/// ```
99+
///
100+
/// ```
101+
/// use block2::global_block;
102+
/// global_block! {
103+
/// pub static MUTATING_BLOCK = |x: &mut i32| {
104+
/// *x = *x + 42;
105+
/// }
106+
/// };
107+
/// let mut x = 5;
108+
/// unsafe { MUTATING_BLOCK.call((&mut x,)) };
109+
/// assert_eq!(x, 47);
110+
/// ```
111+
///
112+
/// The following does not compile because [`Box`] is not [`Encode`]:
113+
///
114+
/// ```compile_fail
115+
/// use block2::global_block;
116+
/// global_block! {
117+
/// pub static INVALID_BLOCK = |b: Box<i32>| {}
118+
/// };
119+
/// ```
120+
#[macro_export]
121+
macro_rules! global_block {
122+
// `||` is parsed as one token
123+
(
124+
$(#[$m:meta])*
125+
$vis:vis static $name:ident = || $(-> $r:ty)? $body:block
126+
) => {
127+
$crate::global_block!($(#[$m])* $vis static $name = |,| $(-> $r)? $body);
128+
};
129+
(
130+
$(#[$m:meta])*
131+
$vis:vis static $name:ident = |$($a:ident: $t:ty),* $(,)?| $(-> $r:ty)? $body:block
132+
) => {
133+
$(#[$m])*
134+
#[allow(unused_unsafe)]
135+
$vis static $name: $crate::GlobalBlock<($($t,)*) $(, $r)?> = unsafe {
136+
$crate::GlobalBlock::from_layout($crate::ffi::Block_layout {
137+
isa: &$crate::ffi::_NSConcreteGlobalBlock as *const _ as *mut _,
138+
flags: $crate::ffi::BLOCK_IS_GLOBAL | $crate::ffi::BLOCK_USE_STRET,
139+
reserved: 0,
140+
invoke: {
141+
unsafe extern "C" fn inner(_: *mut $crate::ffi::Block_layout, $($a: $t),*) $(-> $r)? {
142+
$body
143+
}
144+
let inner: unsafe extern "C" fn(*mut $crate::ffi::Block_layout, $($a: $t),*) $(-> $r)? = inner;
145+
146+
// TODO: SAFETY
147+
::core::mem::transmute(inner)
148+
},
149+
descriptor: &$crate::__GLOBAL_DESCRIPTOR as *const _ as *mut _,
150+
})
151+
};
152+
};
153+
}
154+
155+
#[cfg(test)]
156+
mod tests {
157+
global_block! {
158+
/// Test comments and visibility
159+
pub(super) static NOOP_BLOCK = || {}
160+
}
161+
162+
global_block! {
163+
/// Multiple arguments + trailing comma
164+
#[allow(unused)]
165+
static BLOCK = |x: i32, y: i32, z: i32, w: i32,| -> i32 {
166+
x + y + z + w
167+
}
168+
}
169+
170+
#[test]
171+
fn test_noop_block() {
172+
unsafe { NOOP_BLOCK.call(()) };
173+
}
174+
175+
#[test]
176+
fn test_defined_in_function() {
177+
global_block!(
178+
static MY_BLOCK = || -> i32 {
179+
42
180+
}
181+
);
182+
assert_eq!(unsafe { MY_BLOCK.call(()) }, 42);
183+
}
184+
}

block2/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ use std::os::raw::{c_int, c_ulong};
6666
pub use block_sys as ffi;
6767
use objc2_encode::{Encode, EncodeArguments, Encoding, RefEncode};
6868

69+
#[macro_use]
70+
mod global;
71+
72+
pub use global::{GlobalBlock, __GLOBAL_DESCRIPTOR};
73+
6974
/// Types that may be used as the arguments to an Objective-C block.
7075
pub trait BlockArguments: Sized {
7176
/// Calls the given `Block` with self as the arguments.

tests/src/lib.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,19 @@ pub fn invoke_add_block(block: &Block<(i32,), i32>, a: i32) -> i32 {
3737
mod tests {
3838
use super::*;
3939
use alloc::string::ToString;
40-
use block2::{ConcreteBlock, RcBlock};
40+
use block2::{global_block, ConcreteBlock, RcBlock};
41+
42+
global_block! {
43+
/// Test `global_block` in an external crate
44+
static MY_BLOCK = || -> i32 {
45+
42
46+
}
47+
}
48+
49+
#[test]
50+
fn test_global_block() {
51+
assert_eq!(invoke_int_block(&MY_BLOCK), 42);
52+
}
4153

4254
#[test]
4355
fn test_call_block() {

0 commit comments

Comments
 (0)