Crate serde_closure[][src]

Serialisable closures.

This library provides macros to wrap closures such that they can serialised and sent between other processes running the same binary.

fn sum_of_squares(input: &[i32]) -> i32 {
    input.dist_iter()
        .map(Fn!(|i:&_| *i * *i))
        .sum()
}

For example, if you have the same binary running on each of a cluster of machines, this library would help you to send closures between them.

This library aims to work in as simple and un-magical a way as possible. It currently requires nightly Rust for the unboxed_closures and fn_traits features (rust issue #29625).

  • There are three macros, FnOnce, FnMut and Fn, corresponding to the three types of Rust closure.
  • The captured variables, i.e. those variables that are referenced by the closure but are declared outside of it, must be explicitly listed.
  • The closure is coerced to a function pointer, which is serialized as an isize relative to a known base address.
  • It is deserialised by adding this isize to the known base address, and transmuting to a function pointer.
  • This is the only necessitation of unsafety, and is reliant upon the function pointer being positioned identically relative to the base in both processes – hence both binaries must be identical.
  • To the best of my knowledge this holds in Rust for a given binary. If somehow the known base and the function pointer are in different objects and are loaded at different relative addresses, then this will fail, very likely as a segfault.
  • A solution in this case would be to compile a statically linked executable – in Rust this currently means adding --target x86_64-unknown-linux-musl or similar to the cargo or rustc command line.

Examples of wrapped closures

Inferred, non-capturing closure:

|a| a+1
FnMut!(|a| a+1)

Annotated, non-capturing closure:

|a: String| -> String { a.to_uppercase() }
FnMut!(|a: String| -> String { a.to_uppercase() })

Inferred closure, capturing num:

let mut num = 0;
move |a| num += a
let mut num = 0;
FnMut!([num] move |a| *num += a)

Note: If any variables are captured then the move keyword must be present. As this is a FnMut closure, num is a mutable reference, and must be dereferenced to use.

Capturing hello requiring extra annotation:

let mut hello = String::new();
move |a| {
    hello = hello.to_uppercase() + a;
    hello.clone()
}
let mut hello = String::new();
FnMut!([hello] move |a| {
    let hello: &mut String = hello;
    *hello = hello.to_uppercase() + a;
    hello.clone()
})

Note: hello needs its type annotated in the closure.

Complex closure, capturing a and b:

let (mut a, mut b) = (1usize, String::from("foo"));
move |c,d:&_,e: &mut _,f:String,g:&String,h:&mut String| {
    *e += a+c+*d;
    a += *e;
    *h += ((b.clone()+f.as_str()+g.as_str())).as_str();
    b += h.as_str();
}
let (mut a, mut b) = (1usize, String::from("foo"));
FnMut!([a,b] move |c:_,d:&_,e:&mut _,f:String,g:&String,h:&mut String| {
    let b: &mut String = b;
    *e += *a+c+*d;
    *a += *e;
    *h += ((b.clone()+f.as_str()+g.as_str())).as_str();
    *b += h.as_str();
})

Cosmetic limitations

As visible above, there are currently some limitations that often necessitate extra annotation that you might typically expect to be redundant.

  • Type inference doesn't work as well as normal, hence extra type annotations might be needed;
  • The captured variables in FnMut and FnRef closures are references, so need to be dereferenced;
  • Types cannot be annotated in the list of captured variables;
  • Either none or all of the closure arguments must be annotated; though _ can be used;
  • The move keyword must be present if any variables are captured.

Macros

Fn

Macro that wraps a closure, evaluating to a Fn struct that implements std::ops::Fn, serde's Serialize and Deserialize, and various convenience traits.

FnMut

Macro that wraps a closure, evaluating to a FnMut struct that implements std::ops::FnMut, serde's Serialize and Deserialize, and various convenience traits.

FnOnce

Macro that wraps a closure, evaluating to a FnOnce struct that implements std::ops::FnOnce, serde's Serialize and Deserialize, and various convenience traits.

Structs

Fn

A struct representing a serialisable closure, created by the Fn macro. Implements std::ops::Fn, serde's Serialize and Deserialize, and various convenience traits.

FnMut

A struct representing a serialisable closure, created by the FnMut macro. Implements std::ops::FnMut, serde's Serialize and Deserialize, and various convenience traits.

FnOnce

A struct representing a serialisable closure, created by the FnOnce macro. Implements std::ops::FnOnce, serde's Serialize and Deserialize, and various convenience traits.

RelativePtr
Text