Expand description
§Apple’s C language extension of blocks
C Blocks are functions which capture their environments, i.e. the
C-equivalent of Rust’s Fn
closures. As they were originally developed
by Apple, they’re often used in Objective-C code. This crate provides
capabilities to create, manage and invoke these blocks, in an ergonomic,
“Rust-centric” fashion.
At a high level, this crate contains four types, each representing different kinds of blocks, and different kinds of ownership.
block2 type | Equivalent Rust type |
---|---|
&Block<dyn Fn() + 'a> | &dyn Fn() + 'a |
RcBlock<dyn Fn() + 'a> | Arc<dyn Fn() + 'a> |
StackBlock<'a, (), (), impl Fn() + 'a> | impl Fn() + 'a |
GlobalBlock<dyn Fn()> | fn item |
For more information on the specifics of the block implementation, see the C language specification and the ABI specification.
§Using blocks
You can create a new block from a closure using RcBlock::new
. This can
then be used to call functions or Objective-C methods that takes a block:
use block2::RcBlock;
let val = 5;
let block = RcBlock::new(move |a, b| a + b + val);
obj.someMethod(&block);
§My block isn’t being run?
Most of the time, blocks are used to do asynchronous work; but just like futures in Rust don’t do anything unless polled, a lot of Apple APIs won’t call your block unless a run loop is active, see that link for more information on how to do so.
§Lifetimes
When dealing with blocks, there can be quite a few lifetimes to keep in mind.
The most important one is the lifetime of the block’s data, i.e. the
lifetime of the data in the closure contained in the block. This lifetime
can be specified as 'f
in &Block<dyn Fn() + 'f>
.
Note that &Block<dyn Fn()>
, without any lifetime specifier, can be a bit
confusing, as the default depends on where it is typed. In function/method
signatures, it defaults to 'static
, but as the type of e.g. a let
binding, the lifetime may be inferred to be something smaller, see the
reference for details. If in doubt, either add a
+ 'static
or + '_
to force an escaping or non-escaping block.
Another lifetime is the lifetime of the currently held pointer, i.e. 'b
in &'b Block<dyn Fn()>
. This lifetime can be safely extended using
Block::copy
, so should prove to be little trouble (of course the
lifetime still can’t be extended past the lifetime of the captured data
above).
Finally, the block’s parameter and return types can also contain
lifetimes, as 'a
and 'r
in &Block<dyn Fn(&'a i32) -> &'r u32>
.
Unfortunately, these lifetimes are quite problematic and unsupported at
the moment, due to Rust trait limitations regarding higher-ranked trait
bounds. If you run into problems with this in a block that takes or
returns a reference, consider using the ABI-compatible NonNull<T>
, or
transmute to a 'static
lifetime.
§Thread safety
Thread-safe blocks are not yet representable in block2
, and as such any
function that requires a thread-safe block must be marked unsafe
.
§Mutability
Blocks are generally assumed to be shareable, and as such can only very rarely be made mutable.
You will likely have to use interior mutability helpers like RefCell
or Cell
instead, see below.
§Transforming FnMut
to a block
Mutable closures differs from immutable ones in part in that they need to avoid re-entrancy.
The below example transforms FnMut
to Fn
using a RefCell
. We
do not include this function as part of the public API of block2
, as the
specifics are very dependent on your use-case, and can be optimized with
e.g. a Cell
if your closure is Copy
or if you do not care about
unwind safety, or with UnsafeCell
if you are able to unsafely
guarantee the absence of re-entrancy.
use std::cell::RefCell;
use block2::RcBlock;
fn fnmut_to_fn(closure: impl FnMut()) -> impl Fn() {
let cell = RefCell::new(closure);
move || {
let mut closure = cell.try_borrow_mut().expect("re-entrant call");
(closure)()
}
}
let mut x = 0;
let b = RcBlock::new(fnmut_to_fn(|| {
x += 1;
}));
b.call(());
b.call(());
drop(b);
assert_eq!(x, 2);
§Transforming FnOnce
to a block
FnOnce
is similar to FnMut
in that we must protect against
re-entrancy, with the addition that it can also only be called once.
Ensuring that it can be called once can be done by taking the closure
out of an Option
as shown in the example below. We can use Cell
instead of RefCell
here, since we never need to put the closure “back”
for later use (like we need to do with FnMut
above).
In certain cases you may be able to do micro-optimizations, namely to use
a ManuallyDrop
, if you wanted to optimize with the assumption that the
block is always called, or unwrap_unchecked
if you wanted to optimize
with the assumption that it is only called once.
use std::cell::Cell;
use block2::RcBlock;
fn fnonce_to_fn(closure: impl FnOnce()) -> impl Fn() {
let cell = Cell::new(Some(closure));
move || {
let closure = cell.take().expect("called twice");
closure()
}
}
let v = vec![1, 2, 3];
let b = RcBlock::new(fnonce_to_fn(move || {
drop(v);
}));
b.call(());
§External functions using blocks
To declare external functions or methods that takes blocks, use
&Block<dyn Fn(Params) -> R>
or Option<&Block<dyn Fn(Args) -> R>>
,
where Params
is the parameter types, and R
is the return type.
For this example, consider the function check_addition
which takes a
single parameter, namely a block that adds two integers, and then checks
that the addition is correct.
Such a function could be written in C like in the following.
#include <cassert>
#include <stdint.h>
#include <Block.h>
void check_addition(int32_t (^block)(int32_t, int32_t)) {
assert(block(5, 8) == 13);
}
An extern "C" { ... }
declaration for that function would then be:
use block2::Block;
extern "C" {
fn check_addition(block: &Block<dyn Fn(i32, i32) -> i32>);
}
This can similarly be done for Objcective-C methods declared with
objc2::extern_methods!
(though most of the time, the framework
crates will take care of that for you).
use block2::Block;
use objc2::extern_methods;
impl MyClass {
extern_methods!(
#[unsafe(method(checkAddition:))]
pub fn checkAddition(&self, block: &Block<dyn Fn(i32, i32) -> i32>);
);
}
If the function/method allows passing NULL
blocks, the type should be
Option<&Block<dyn Fn(i32, i32) -> i32>>
instead.
§Invoking blocks
We can also define the external function in Rust, and expose it to
Objective-C. To do this, we can use Block::call
to invoke the block
inside the function.
use block2::Block;
#[no_mangle]
extern "C" fn check_addition(block: &Block<dyn Fn(i32, i32) -> i32>) {
assert_eq!(block.call((5, 8)), 13);
}
Note the extra parentheses in the call
method, since the arguments must
be passed as a tuple.
§Specifying a runtime
Different runtime implementations exist and act in slightly different ways (and have several different helper functions), the most important aspect being that the libraries are named differently, so we must take that into account when linking.
You can choose the desired runtime by using the relevant cargo feature flags, see the following sections.
§Apple’s libclosure
This is the most common and most sophisticated runtime, and it has quite a lot more features than the specification mandates.
The minimum required operating system versions are as follows (though in practice Rust itself requires higher versions than this):
- macOS:
10.6
- iOS/iPadOS:
3.2
- tvOS:
1.0
- watchOS:
1.0
This is used by default, so you do not need to specify a runtime if you’re using this crate on of these platforms.
§LLVM compiler-rt
’s libBlocksRuntime
- Feature flag:
compiler-rt
.
This is a copy of Apple’s older (around macOS 10.6) runtime, and is now
used in Swift’s libdispatch
and Swift’s Foundation as well.
The runtime and associated headers can be installed on many Linux systems
with the libblocksruntime-dev
package.
Using this runtime probably won’t work together with objc2
crate.
§GNUStep’s libobjc2
- Feature flag:
gnustep-1-7
,gnustep-1-8
,gnustep-1-9
,gnustep-2-0
andgnustep-2-1
depending on the version you’re using.
GNUStep is a bit odd, because it bundles blocks support into its
Objective-C runtime. This means we have to link to libobjc
, and this is
done by depending on the objc2
crate. A bit unorthodox, yes, but it
works.
Sources:
§Microsoft’s WinObjC
- Feature flag:
unstable-winobjc
.
Unstable: Hasn’t been tested on Windows yet!
A fork based on GNUStep’s libobjc2
version 1.8.
§ObjFW
- Feature flag:
unstable-objfw
.
Unstable: Doesn’t work yet!
§C Compiler configuration
To our knowledge, only Clang supports blocks. To compile C or Objective-C
sources using these features, you should set the -fblocks
flag.
Modules§
- ffi
- Raw bindings to
Block.h
Macros§
- global_
block - Construct a static
GlobalBlock
.
Structs§
- Block
- An opaque type that holds an Objective-C block.
- Global
Block - A global Objective-C block that does not capture an environment.
- RcBlock
- A reference-counted Objective-C block that is stored on the heap.
- Stack
Block - An Objective-C block constructed on the stack.
Traits§
- BlockFn
- Types that represent closure parameters/arguments and return types in a block.
- Into
Block - Types that may be converted into a block.
- Manual
Block Encoding - Interim abstraction to manually provide block encodings for use at compile
time with
StackBlock::with_encoding
andRcBlock::with_encoding
.
Type Aliases§
- Concrete
Block Deprecated - Deprecated alias for a
'static
StackBlock
. - DynBlock
- Helper type to allow changing
Block
in the future without affecting framework crates.