2 releases

0.1.2 Apr 21, 2019
0.1.1 Jan 10, 2018
0.1.0 Jan 10, 2018

#55 in Asynchronous

Download history 1319/week @ 2020-02-09 1152/week @ 2020-02-16 1167/week @ 2020-02-23 1065/week @ 2020-03-01 1180/week @ 2020-03-08 1754/week @ 2020-03-15 1317/week @ 2020-03-22 1330/week @ 2020-03-29 1469/week @ 2020-04-05 1352/week @ 2020-04-12 1907/week @ 2020-04-19 2795/week @ 2020-04-26 2123/week @ 2020-05-03 2330/week @ 2020-05-10 1869/week @ 2020-05-17 1815/week @ 2020-05-24

6,203 downloads per month
Used in 415 crates (184 directly)

MIT/Apache

16KB
106 lines

nb

Minimal and reusable non-blocking I/O layer

Documentation

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.


lib.rs:

Minimal and reusable non-blocking I/O layer

The ultimate goal of this crate is code reuse. With this crate you can write core I/O APIs that can then be adapted to operate in either blocking or non-blocking manner. Furthermore those APIs are not tied to a particular asynchronous model and can be adapted to work with the futures model or with the async / await model.

Core idea

The WouldBlock error variant signals that the operation can't be completed right now and would need to block to complete. WouldBlock is a special error in the sense that's not fatal; the operation can still be completed by retrying again later.

nb::Result is based on the API of std::io::Result, which has a WouldBlock variant in its ErrorKind.

We can map WouldBlock to different blocking and non-blocking models:

How to use this crate

Application specific errors can be put inside the Other variant in the nb::Error enum.

So in your API instead of returning Result<T, MyError> return nb::Result<T, MyError>

enum MyError {
    ThisError,
    ThatError,
    // ..
}

// This is a blocking function, so it returns a normal `Result`
fn before() -> Result<(), MyError> {
    // ..
#   Ok(())
}

// This is now a potentially (read: *non*) blocking function so it returns `nb::Result`
// instead of blocking
fn after() -> nb::Result<(), MyError> {
    // ..
#   Ok(())
}

You can use the never type (!) to signal that some API has no fatal errors but may block:

#![feature(never_type)]

// This returns `Ok(())` or `Err(nb::Error::WouldBlock)`
fn maybe_blocking_api() -> nb::Result<(), !> {
    // ..
#   Ok(())
}

Once your API uses nb::Result you can leverage the block!, try_nb! and await! macros to adapt it for blocking operation, or for non-blocking operation with futures or await.

NOTE Currently, both try_nb! and await! are feature gated behind the unstable Cargo feature.

Examples

A Core I/O API

Imagine the code (crate) below represents a Hardware Abstraction Layer for some microcontroller (or microcontroller family).

In this and the following examples let's assume for simplicity that peripherals are treated as global singletons and that no preemption is possible (i.e. interrupts are disabled).

#![feature(never_type)]

// This is the `hal` crate
// Note that it doesn't depend on the `futures` crate

extern crate nb;

/// An LED
pub struct Led;

impl Led {
    pub fn off(&self) {
        // ..
    }
    pub fn on(&self) {
        // ..
    }
}

/// Serial interface
pub struct Serial;
pub enum Error {
    Overrun,
    // ..
}

impl Serial {
    /// Reads a single byte from the serial interface
    pub fn read(&self) -> nb::Result<u8, Error> {
        // ..
#       Ok(0)
    }

    /// Writes a single byte to the serial interface
    pub fn write(&self, byte: u8) -> nb::Result<(), Error> {
        // ..
#       Ok(())
    }
}

/// A timer used for timeouts
pub struct Timer;

impl Timer {
    /// Waits until the timer times out
    pub fn wait(&self) -> nb::Result<(), !> {
        //^ NOTE the `!` indicates that this operation can block but has no
        //  other form of error

        // ..
#       Ok(())
    }
}

Blocking mode

Turn on an LED for one second and then loops back serial data.

# #![feature(never_type)]
#[macro_use(block)]
extern crate nb;

use hal::{Led, Serial, Timer};

fn main() {
    // Turn the LED on for one second
    Led.on();
    block!(Timer.wait()).unwrap(); // NOTE(unwrap) E = !
    Led.off();

    // Serial interface loopback
    # return;
    loop {
        let byte = block!(Serial.read()).unwrap();
        block!(Serial.write(byte)).unwrap();
    }
}

# mod hal {
#   use nb;
#   pub struct Led;
#   impl Led {
#       pub fn off(&self) {}
#       pub fn on(&self) {}
#   }
#   pub struct Serial;
#   impl Serial {
#       pub fn read(&self) -> nb::Result<u8, ()> { Ok(0) }
#       pub fn write(&self, _: u8) -> nb::Result<(), ()> { Ok(()) }
#   }
#   pub struct Timer;
#   impl Timer {
#       pub fn wait(&self) -> nb::Result<(), !> { Ok(()) }
#   }
# }

futures

Blinks an LED every second and loops back serial data. Both tasks run concurrently.

#![feature(conservative_impl_trait)]
#![feature(never_type)]

extern crate futures;
#[macro_use(try_nb)]
extern crate nb;

use futures::{Async, Future};
use futures::future::{self, Loop};
use hal::{Error, Led, Serial, Timer};

/// `futures` version of `Timer.wait`
///
/// This returns a future that must be polled to completion
fn wait() -> impl Future<Item = (), Error = !> {
    future::poll_fn(|| {
        Ok(Async::Ready(try_nb!(Timer.wait())))
    })
}

/// `futures` version of `Serial.read`
///
/// This returns a future that must be polled to completion
fn read() -> impl Future<Item = u8, Error = Error> {
    future::poll_fn(|| {
        Ok(Async::Ready(try_nb!(Serial.read())))
    })
}

/// `futures` version of `Serial.write`
///
/// This returns a future that must be polled to completion
fn write(byte: u8) -> impl Future<Item = (), Error = Error> {
    future::poll_fn(move || {
        Ok(Async::Ready(try_nb!(Serial.write(byte))))
    })
}

fn main() {
    // Tasks
    let mut blinky = future::loop_fn::<_, (), _, _>(true, |state| {
        wait().map(move |_| {
            if state {
                Led.on();
            } else {
                Led.off();
            }

            Loop::Continue(!state)
        })
    });

    let mut loopback = future::loop_fn::<_, (), _, _>((), |_| {
        read().and_then(|byte| {
            write(byte)
        }).map(|_| {
            Loop::Continue(())
        })
    });

    // Event loop
    loop {
        blinky.poll().unwrap(); // NOTE(unwrap) E = !
        loopback.poll().unwrap();
        # break
    }
}

# mod hal {
#   use nb;
#   pub struct Led;
#   impl Led {
#       pub fn off(&self) {panic!()}
#       pub fn on(&self) {}
#   }
#   #[derive(Debug)]
#   pub enum Error {}
#   pub struct Serial;
#   impl Serial {
#       pub fn read(&self) -> nb::Result<u8, Error> { Err(nb::Error::WouldBlock) }
#       pub fn write(&self, _: u8) -> nb::Result<(), Error> { Err(nb::Error::WouldBlock) }
#   }
#   pub struct Timer;
#   impl Timer {
#       pub fn wait(&self) -> nb::Result<(), !> { Err(nb::Error::WouldBlock) }
#   }
# }

await!

This is equivalent to the futures example but with much less boilerplate.

#![feature(generator_trait)]
#![feature(generators)]
#![feature(never_type)]

#[macro_use(await)]
extern crate nb;

use std::ops::Generator;

use hal::{Led, Serial, Timer};

fn main() {
    // Tasks
    let mut blinky = || {
        let mut state = false;
        loop {
            // `await!` means suspend / yield instead of blocking
            await!(Timer.wait()).unwrap(); // NOTE(unwrap) E = !

            state = !state;

            if state {
                 Led.on();
            } else {
                 Led.off();
            }
        }
    };

    let mut loopback = || {
        loop {
            let byte = await!(Serial.read()).unwrap();
            await!(Serial.write(byte)).unwrap();
        }
    };

    // Event loop
    loop {
        blinky.resume();
        loopback.resume();
        # break
    }
}

# mod hal {
#   use nb;
#   pub struct Led;
#   impl Led {
#       pub fn off(&self) {}
#       pub fn on(&self) {}
#   }
#   pub struct Serial;
#   impl Serial {
#       pub fn read(&self) -> nb::Result<u8, ()> { Err(nb::Error::WouldBlock) }
#       pub fn write(&self, _: u8) -> nb::Result<(), ()> { Err(nb::Error::WouldBlock) }
#   }
#   pub struct Timer;
#   impl Timer {
#       pub fn wait(&self) -> nb::Result<(), !> { Err(nb::Error::WouldBlock) }
#   }
# }

No runtime deps