[][src]Crate embedded_timeout_macros

Useful macros for working with timeouts on top of embedded-hal APIs

The non-blocking APIs in the embedded-hal crate use nb::Result from the nb crate to signal whether an operation has finished. This can be a bit tedious to work with in non-trivial cases, for example if timeouts are involved.

This crate defines macros that help working with timeouts on top of embedded-hal APIs.

Why Macros?

A similar result could be achieved using functions and closures, so "Why use macros for this?" is a fair question. While macros can be confusing and hard to work with, they are also more powerful. Here are some things you can do using the macros in this crate, that you couldn't do with a function/closure-based approach:

  • You can return the current function, from within the closure.
  • You can break/continue an outer loop, from within the closure.
  • You can rely on the compiler's reasoning to support complicated moves.

That last point is the least obvious, so it deserves some elaboration. Take the following example:

let mut thing_being_idle = Thing::new();

loop {
    // `do_stuff` takes ownership of the idle thing and returns a new type
    // that represents the same thing, but no longer being idle and doing
    // stuff.
    let thing_doing_stuff = thing_being_idle.do_stuff();

    // stuff is being done

    // `finish_doing_stuff` again takes ownership of the thing, and returns
    // the original type that represents the thing while it's being idle.
    // We move that new idle thing into the original variable.
    //
    // The compiler understands this, so even though we've moved out of
    // `thing_being_idle`, it's going to be available again in the next loop
    // iteration.
    thing_being_idle = thing_doing_stuff.finish_doing_stuff();
}


struct Thing<State>(State);

impl Thing<BeingIdle> {
    fn new() -> Self {
        Thing(BeingIdle)
    }

    fn do_stuff(self) -> Thing<DoingStuff> {
        // Start doing the important stuff
        // ...

        Thing(DoingStuff)
    }
}

impl Thing<DoingStuff> {
    fn finish_doing_stuff(self) -> Thing<BeingIdle> {
        // Finish doing the important stuff
        // ...

        Thing(BeingIdle)
    }
}

struct BeingIdle;
struct DoingStuff;

Since the macros in this crate are basically just fancy loops that don't do anything complicated, the principle demonstrated above fully applies when using them.

Contrast that with a closure-based approach:

This example is not tested
let mut thing_being_idle = Thing::new();

loop {
    let closure = || {
        // Since `do_stuff` takes ownership of the idle thing, the whole
        // closure takes ownership. We'll actually get a compiler error
        // here, as the compiler doesn't really understand that the closure
        // also gives this ownership back. See comment below.
        let thing_doing_stuff = thing_being_idle.do_stuff();

        // stuff is being done

        // Like in the example above, we try to give ownership back, so we
        // can use the variable again in the next loop iteration. However,
        // the compiler doesn't seem to have a concept of closures giving
        // back ownership, so it won't understand this, and the whole
        // example will not compile.
        thing_being_idle = thing_doing_stuff.finish_doing_stuff();
    };

    closure();
}

Re-exports

pub use embedded_hal;
pub use nb;

Macros

block_timeout

Blocks on a non-blocking operation until a timer times out

repeat_timeout

Repeats an operation until a timer times out

Enums

TimeoutError

An error that can either be a timeout or another error