[][src]Crate union

union!

union! - one macro to rule them all. Combines sync/async results, transforms tuple of results in result of tuple, provides single and multi thread (sync/async) step by step execution of branches and useful shortcut combinators.

Combinators

  • Map: |> expr - value.map(expr)

  • AndThen: => expr - value.and_then(expr),

  • Then: -> expr - expr(value)

  • Dot: >. expr - value.expr

  • Or: <| expr - value.or(expr)

  • OrElse: <= expr - value.or_else(expr)

  • MapErr: !> expr - value.map_err(expr)

  • Inspect: ?> expr - (|value| { expr(value); value })(value)

where value is the previous value.

Every combinator prefixed by ~ will act as deferred action (all actions will wait until completion in every step and only after move to the next one).

Handler

might be one of

  • map => will act as results.map(|(result0, result1, ..)| handler(result0, result1, ..))

  • and_then => will act as results.and_then(|(result0, result1, ..)| handler(result0, result1, ..))

  • then => will act as handler(result0, result1, ..)

or not specified - then Result<(result0, result1, ..), Error> or Option<(result0, result1, ..)> will be returned.

Single thread combinations

Simple sync results combination

Converts input in series of chained results and joins them step by step.

extern crate union;
 
use std::error::Error;
use union::union;
 
type Result<T> = std::result::Result<T, Box<dyn Error>>;
 
fn action_1() -> Result<u16> {
    Ok(1)
}
 
fn action_2() -> Result<u8> {
    Ok(2)
}
 
fn main() {
    let sum = union! {
        action_1(),
        action_2().map(|v| v as u16),
        action_2().map(|v| v as u16 + 1).and_then(|v| Ok(v * 4)),
        action_1().and_then(|_| Err("5".into())).or(Ok(2)),
        map => |a, b, c, d| a + b + c + d
    }.expect("Failed to calculate sum");
 
    println!("Calculated: {}", sum);
}

Async results combination

Each branch will represent chain of tasks. All branches will be joined using join! macro and macro will return unpolled future.

#![recursion_limit="256"]
 
extern crate union;
extern crate futures;
extern crate tokio;
 
use std::error::Error;
use union::union_async;
use futures::future::{ok, err};
 
type Result<T> = std::result::Result<T, Box<dyn Error>>;
 
async fn action_1() -> Result<u16> {
    Ok(1)
}
async fn action_2() -> Result<u8> {
    Ok(2)
}
 
#[tokio::main]
async fn main() {
    let sum = union_async! {
        action_1(),
        action_2().and_then(|v| ok(v as u16)),
        action_2().map(|v| v.map(|v| v as u16 + 1)).and_then(|v| ok(v * 4u16)),
        action_1().and_then(|_| err("5".into())).or_else(|_| ok(2u16)),
        and_then => |a, b, c, d| ok(a + b + c + d)
    }.await.expect("Failed to calculate sum");
  
    println!("Calculated: {}", sum);
}

Multi-thread combinations

To execute several tasks in parallel you could use union_spawn! (spawnion!) for sync tasks and union_async_spawn! (spasyncion!) for async tasks. Since union_async already provides parallel futures execution in one thread, union_async_spawn! spawns every branch into tokio executor so they will be evaluated in multi-threaded executor.

Multi-thread sync branches

union_spawn spawns one ::std::thread per each step of each branch (number of branches is the max thread count at the time).

extern crate union;
 
use std::error::Error;
use union::union_spawn;
 
type Result<T> = std::result::Result<T, Box<dyn Error + Send + Sync>>;
 
fn action_1() -> Result<usize> {
    Ok(1)
}
 
fn action_2() -> Result<u16> {
    Ok(2)
}
 
fn main() {
    // Branches will be executed in parallel
    let sum = union_spawn! {
        action_1(),
        action_2().map(|v| v as usize),
        action_2().map(|v| v as usize + 1).and_then(|v| Ok(v * 4)),
        action_1().and_then(|_| Err("5".into())).or(Ok(2)),
        map => |a, b, c, d| a + b + c + d
    }.expect("Failed to calculate sum");
 
    println!("Calculated: {}", sum);
}

union_async_spawn! uses ::tokio::spawn function to spawn tasks so it should be done inside tokio runtime (number of branches is the max count of tokio tasks at the time).

Multi-thread async branches

#![recursion_limit="256"]
extern crate union;
extern crate futures;
extern crate tokio;
  
use std::error::Error;
use union::union_async_spawn;
use futures::future::{ok, err};
  
type Result<T> = std::result::Result<T, Box<dyn Error + Send + Sync>>;
  
async fn action_1() -> Result<u16> {
     Ok(1)
}
  
async fn action_2() -> Result<u8> {
     Ok(2)
}
  
#[tokio::main]
async fn main() {
     let sum = union_async_spawn! {
         action_1(),
         action_2().and_then(|v| ok(v as u16)),
         action_2().map(|v| v.map(|v| v as u16 + 1)).and_then(|v| ok(v * 4u16)),
         action_1().and_then(|_| err("5".into())).or_else(|_| ok(2u16)),
         and_then => |a, b, c, d| ok(a + b + c + d)
     }.await.expect("Failed to calculate sum");
  
     println!("Calculated: {}", sum);
}

Using combinators we can rewrite first example like

extern crate union;
 
use std::error::Error;
use union::union;
 
type Result<T> = std::result::Result<T, Box<dyn Error>>;
 
fn action_1() -> Result<u16> {
    Ok(1)
}
 
fn action_2() -> Result<u8> {
    Ok(2)
}
 
fn main() {
    let sum = union! {
        action_1(),
        action_2() |> |v| v as u16,
        action_2() |> |v| v as u16 + 1 => |v| Ok(v * 4),
        action_1() => |_| Err("5".into()) <| Ok(2),
        map => |a, b, c, d| a + b + c + d
    }.expect("Failed to calculate sum");
 
    println!("Calculated: {}", sum);
}

By separating chain in actions, you will make actions wait for completion of all of them in current step before go to the next step.

extern crate union;
 
use std::error::Error;
use union::union;
 
type Result<T> = std::result::Result<T, u8>;
 
fn action_1() -> Result<u16> {
    Ok(1)
}
 
fn action_2() -> Result<u8> {
    Ok(2)
}
 
fn main() {
    let sum = union! {
        action_1(),
        let result_1 = action_2() ~|> |v| v as u16 + 1,
        action_2() ~|> move |v| {
            // `result_1` now is the result of `action_2()` [Ok(1u8)]
            if result_1.is_ok() { 
                v as u16 + 1 
            } else {
                unreachable!() 
            }
        } ~=> move |v| {
            // `result_1` now is the result of `|v| v as u16 + 1` [Ok(2u16)]
            if let Ok(result_1) = result_1 {
                Ok(v * 4 + result_1)
            } else {
                unreachable!()
            }
        },
        action_1() ~=> |_| Err(5) <| Ok(2),
        map => |a, b, c, d| a + b + c + d
    }.expect("Failed to calculate sum");
    println!("Calculated: {}", sum);
}
 

Macros

asyncion
spasyncion
spawnion
union
union_async
union_async_spawn
union_spawn