Crate crossmist

source ·
Expand description

Efficient and seamless cross-process communication, providing semantics similar to std::thread::spawn and alike, both synchronously and asynchronously (via tokio).

This crate allows you to easily perform computations in another process without creating a separate executable or parsing command line arguments manually. For example, the simplest example, computing a sum of several numbers in a one-shot subprocess, looks like this:

#[crossmist::main]
fn main() {
    println!("5 + 7 = {}", add.run(vec![5, 7]).unwrap());
}

#[crossmist::func]
fn add(nums: Vec<i32>) -> i32 {
    nums.into_iter().sum()
}

This crate also supports long-lived tasks with constant cross-process communication:

#[crossmist::main]
fn main() {
    let (mut ours, theirs) = crossmist::duplex().unwrap();
    add.spawn(theirs).expect("Failed to spawn child");
    for i in 1..=5 {
        for j in 1..=5 {
            println!("{i} + {j} = {}", ours.request(&vec![5, 7]).unwrap());
        }
    }
}

#[crossmist::func]
fn add(mut chan: crossmist::Duplex<i32, Vec<i32>>) {
    while let Some(nums) = chan.recv().unwrap() {
        chan.send(&nums.into_iter().sum());
    }
}

Passing objects

Almost arbitrary objects can be passed between processes and across channels, including file handles, sockets, and other channels.

For numeric types, strings, vectors, hashmaps, other common containers, and files/sockets, the Object trait is implemented automatically. For user-defined structures and enums, use #[derive(Object)]. You may use generics, but make sure to add : Object constraint to stored types:

use crossmist::Object;

#[derive(Object)]
struct MyPair<T: Object, U: Object> {
    first: T,
    second: U,
}

Occasionally, e.g. for custom hash tables or externally defined types, you might have to implement Object manually. Check out the documentation for Object for more information.

Channels

As the second example demonstrates, cross-process communication may be achieved not only via arguments and return values, but via long-lived channels. Channels may be unidirectional (one process has a Sender instance and another process has a connected Receiver instance) or bidirectional (both processes have Duplex instances). Channels are typed: you don’t just send byte streams à la TCP, you send objects of a well-defined type implementing the Object trait, making channels type-safe.

Channels implement Object. This means that not only can you pass channels to subprocesses as arguments (they wouldn’t be useful otherwise), but you can pass channels across other channels, just like you can pass files across channels.

Channels are trusted. This means that if one side reads from Receiver and another side writes garbage to the corresponding file descriptor instead of using Sender, the receiver side may crash and burn, potentially leading to arbitrary code execution.

The communication protocol is not fixed and may not only change in minor versions, but be architecture- or build-dependent. This is done to both ensure performance optimizations can be implemented and to let us fix bugs quickly when they arise. As channels may only be used between two processes started from the same executable file, this does not violate semver.

Re-exports

Modules

  • A wrapper for objects that require global state to be configured before deserialization.
  • Utilities for passing function callbacks between processes.
  • Cross-platform alternative to file descriptors.
  • Uni- and bidirectional channels between processes.
  • Serialization and deserialization.
  • Management of child processes.
  • tokiotokio
    Asynchronous implementation using tokio runtime.

Macros

  • A short-cut for turning a (possible capturing) closure into an object function, just like as if #[func] was used.

Structs

  • The subprocess object created by calling spawn on a function annottated with #[func].
  • A side of a bidirectional channel.
  • The receiving side of a unidirectional channel.
  • The transmitting side of a unidirectional channel.

Functions

  • Create a unidirectional channel.
  • Create a bidirectional channel.

Attribute Macros

  • Enable a function to be used as an entrypoint of a child process, and turn it into an Object.
  • Setup an entrypoint.

Derive Macros

  • Make a structure or a enum serializable.