Crate block2

Source
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 typeEquivalent 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 and gnustep-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.
GlobalBlock
A global Objective-C block that does not capture an environment.
RcBlock
A reference-counted Objective-C block that is stored on the heap.
StackBlock
An Objective-C block constructed on the stack.

Traits§

BlockFn
Types that represent closure parameters/arguments and return types in a block.
IntoBlock
Types that may be converted into a block.
ManualBlockEncoding
Interim abstraction to manually provide block encodings for use at compile time with StackBlock::with_encoding and RcBlock::with_encoding.

Type Aliases§

ConcreteBlockDeprecated
Deprecated alias for a 'static StackBlock.
DynBlock
Helper type to allow changing Block in the future without affecting framework crates.