#[func]
Expand description
Enable a function to be used as an entrypoint of a child process, and turn it into an
Object
.
This macro applies to fn
functions, including generic ones. It adds various methods for
spawning a child process from this function.
For a function declared as
#[func]
fn example(arg1: Type1, ...) -> Output;
…the methods are:
pub fn spawn(&self, arg1: Type1, ...) -> std::io::Result<crossmist::Child<Output>>;
pub fn run(&self, arg1: Type1, ...) -> std::io::Result<Output>;
spawn
runs the function in a subprocess and returns a Child
instance which can be used to
monitor the process and retrieve its return value when it finishes via Child::join
. run
combines the two operations into one, which may be useful if a new process is needed for a
reason other than parallel execution.
For example:
use crossmist::{func, main};
#[func]
fn example(a: i32, b: i32) -> i32 {
a + b
}
#[main]
fn main() {
assert_eq!(example.spawn(5, 7).unwrap().join().unwrap(), 12);
assert_eq!(example.run(5, 7).unwrap(), 12);
}
The function can also be invoked in the same process via the FnOnceObject
,
FnMutObject
, and FnObject
traits, which are similar to std::ops::FnOnce
,
std::ops::FnMut
, and std::ops::Fn
, respectively:
use crossmist::{FnObject, func, main};
#[func]
fn example(a: i32, b: i32) -> i32 {
a + b
}
#[main]
fn main() {
assert_eq!(example.call_object((5, 7)), 12);
}
If the nightly
feature is enabled, the function can also directly be called, providing the
same behavior as if #[func]
was not used:
use crossmist::{FnObject, func, main};
#[func]
fn example(a: i32, b: i32) -> i32 {
a + b
}
#[main]
fn main() {
assert_eq!(example(5, 7), 12);
}
spawn
and run
return an error if spawning the child process failed (e.g. the process limit
is exceeded or the system lacks memory). run
also returns an error if the process panics,
calls std::process::exit
or alike instead of returning a value, or is terminated (as does
Child::join
).
The child process relays its return value to the parent via an implicit channel. Therefore, it
is important to keep the Child
instance around until the child process terminates and never
drop it before joining, or the child process will panic.
Do:
#[crossmist::main]
fn main() {
let child = long_running_task.spawn().expect("Failed to spawn child");
// ...
let need_child_result = false; // assume this is computed from some external data
// ...
let return_value = child.join().expect("Child died");
if need_child_result {
eprintln!("{return_value}");
}
}
#[crossmist::func]
fn long_running_task() -> u32 {
std::thread::sleep(std::time::Duration::from_secs(1));
123
}
Don’t:
#[crossmist::main]
fn main() {
let child = long_running_task.spawn().expect("Failed to spawn child");
// ...
let need_child_result = false; // assume this is computed from some external data
// ...
if need_child_result {
eprintln!("{}", child.join().expect("Child died"));
}
}
#[crossmist::func]
fn long_running_task() -> u32 {
std::thread::sleep(std::time::Duration::from_secs(1));
123
}
The void return type (()
) is an exception to this rule: such return values are not delivered,
and thus Child
may be safely dropped at any point, and the child process is allowed to use
std::process::exit
instead of explicitly returning ()
.
Do:
#[crossmist::main]
fn main() {
long_running_task.spawn().expect("Failed to spawn child");
}
#[crossmist::func]
fn long_running_task() {
std::thread::sleep(std::time::Duration::from_secs(1));
}
Do:
#[crossmist::main]
fn main() {
let child = long_running_task.spawn().expect("Failed to spawn child");
// ...
child.join().expect("Child died");
}
#[crossmist::func]
fn long_running_task() {
std::thread::sleep(std::time::Duration::from_secs(1));
std::process::exit(0);
}
§Asynchronous case
If the tokio
feature is enabled, the following methods are also made available:
pub async fn spawn_tokio(&self, arg1: Type1, ...) ->
std::io::Result<crossmist::tokio::Child<Output>>;
pub async fn run_tokio(&self, arg1: Type1, ...) -> std::io::Result<Output>;
If smol
is enabled, the functions spawn_smol
and run_smol
with matching signatures are
generated.
Additionally, the function may be async
. In this case, you have to indicate which runtime to
use as follows:
#[crossmist::func(tokio)]
async fn example_tokio() {}
#[crossmist::func(smol)]
async fn example_smol() {}
You may pass operands to forward to tokio::main
like this:
#[crossmist::func(tokio(flavor = "current_thread"))]
async fn example() {}
Notice that the use of spawn
vs spawn_tokio
/spawn_smol
is orthogonal to whether the
function is async
: you can start a synchronous function in a child process asynchronously, or
vice versa:
use crossmist::{func, main};
#[func]
fn example(a: i32, b: i32) -> i32 {
a + b
}
#[main]
#[tokio::main(flavor = "current_thread")]
async fn main() {
assert_eq!(example.run_tokio(5, 7).await.unwrap(), 12);
}
use crossmist::{func, main};
#[func(tokio(flavor = "current_thread"))]
async fn example(a: i32, b: i32) -> i32 {
a + b
}
#[main]
fn main() {
assert_eq!(example.run(5, 7).unwrap(), 12);
}