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.

§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.

In the next few examples, we’re going to work with a function check_addition, that takes as parameter a block that adds two integers, and 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 inside external methods declared with objc2::extern_methods!.

use block2::Block;
use objc2::extern_methods;

extern_methods!(
    unsafe impl MyClass {
        #[method(checkAddition:)]
        pub fn checkAddition(&self, block: &Block<dyn Fn(i32, i32) -> i32>);
    }
);

If the function/method allowed passing NULL blocks, the type would 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.

§Creating blocks

Creating a block to pass to Objective-C can be done with RcBlock or StackBlock, depending on if you want to move the block to the heap, or let the callee decide if it needs to do that.

To call such a function / method, we could create a new block from a closure using RcBlock::new.

use block2::RcBlock;

let block = RcBlock::new(|a, b| a + b);
check_addition(&block);

This creates the block on the heap. If the external function you’re calling is not going to copy the block, it may be more performant if you construct a StackBlock directly, using StackBlock::new.

Note that this requires that the closure is Clone, as the external code is allowed to copy the block to the heap in the future.

use block2::StackBlock;

let block = StackBlock::new(|a, b| a + b);
check_addition(&block);

As an optimization, if your closure doesn’t capture any variables (as in the above examples), you can use the global_block! macro to create a static block.

use block2::global_block;

global_block! {
    static BLOCK = |a: i32, b: i32| -> i32 {
        a + b
    };
}

check_addition(&BLOCK);

§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. In particular, there is no good way to prevent re-entrancy.

You will likely have to use interior mutability instead.

§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 (you might have to disable the default "apple" feature first).

§Apple’s libclosure

  • Feature flag: apple.

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§

  • Raw bindings to Block.h

Macros§

Structs§

  • An opaque type that holds an Objective-C block.
  • A global Objective-C block that does not capture an environment.
  • A reference-counted Objective-C block that is stored on the heap.
  • An Objective-C block constructed on the stack.

Traits§

  • Types that represent closure parameters/arguments and return types in a block.
  • Types that may be converted into a block.

Type Aliases§

  • ConcreteBlockDeprecated
    Deprecated alias for a 'static StackBlock.