[][src]Module drone_core::inventory

A zero-cost abstraction to track various resource states with the type-system.

Lets describe the pattern by example. (Familiarity with [token] module may be required.) Imagine that we need to implement a DMA driver. The DMA peripheral consists of the common functionality, which includes the power switch for the whole peripheral, and separate DMA channels. The channels can be used independently in different threads. We want to avoid situations where one thread holding the switch breaks the other thread holding a channel. Lets see an example of the pattern:

use core::sync::atomic::{AtomicBool, Ordering};
use drone_core::{
    inventory::{self, Inventory0, Inventory1},
    token::{simple_token, Token},
};

// Let it be our power switch, so we can easily observe its state.
static DMA_EN: AtomicBool = AtomicBool::new(false);

// Our drivers map unique resources expressed by tokens.
simple_token!(pub struct DmaReg);
simple_token!(pub struct DmaChReg);

// We split the DMA driver in two types: one for disabled state, and the
// other for enabled state.
pub struct Dma(Inventory0<DmaEn>);
pub struct DmaEn(DmaReg);

impl Dma {
    // The constructor for the DMA driver. Note that `reg` is a token, so at most
    // one instance of the driver could ever exist.
    pub fn new(reg: DmaReg) -> Self {
        Self(Inventory0::new(DmaEn(reg)))
    }

    // It is always a good idea to provide a method to free the token passed to
    // the `new()` method above.
    pub fn free(self) -> DmaReg {
        Inventory0::free(self.0).0
    }

    // This method takes `self` by reference and returns a scoped guard object. It
    // enables DMA, and the returned guard will automatically disable it on `drop`.
    pub fn enable(&mut self) -> inventory::Guard<'_, DmaEn> {
        self.setup();
        Inventory0::guard(&mut self.0)
    }

    // This method takes `self` by value and returns the inventory object with one
    // token taken. It enables DMA, and in order to disable it, one should
    // explicitly call  `from_enabled()` method below.
    pub fn into_enabled(self) -> Inventory1<DmaEn> {
        self.setup();
        let (enabled, token) = self.0.share1();
        // To be recreated in `from_enabled()`.
        drop(token);
        enabled
    }

    // This method takes the inventory object with one token taken, restores the
    // token, and disables DMA.
    pub fn from_enabled(enabled: Inventory1<DmaEn>) -> Self {
        // Restoring the token dropped in `into_enabled()`.
        let token = unsafe { inventory::Token::new() };
        let mut enabled = enabled.merge1(token);
        Inventory0::teardown(&mut enabled);
        Self(enabled)
    }

    // An example method, which can be called only when DMA is disabled.
    pub fn do_something_with_disabled_dma(&self) {}

    // A private method that actually enables DMA.
    fn setup(&self) {
        DMA_EN.store(true, Ordering::Relaxed);
    }
}

impl inventory::Item for DmaEn {
    // A method that disables DMA. Due to its signature it can't be called directly.
    // It is called only by `Guard::drop` or `Inventory0::teardown`.
    fn teardown(&mut self, _token: &mut inventory::GuardToken<DmaEn>) {
        DMA_EN.store(false, Ordering::Relaxed);
    }
}

impl DmaEn {
    // An example method, which can be called only when DMA is enabled.
    fn do_something_with_enabled_dma(&self) {}
}

// Here we define types for DMA channels.
pub struct DmaCh(DmaChEn);
pub struct DmaChEn(DmaChReg);

impl DmaCh {
    // The following two methods are the usual constructor and destructor.

    pub fn new(reg: DmaChReg) -> Self {
        Self(DmaChEn(reg))
    }

    pub fn free(self) -> DmaChReg {
        (self.0).0
    }

    // A DMA channel is enabled when the whole DMA is enabled. If we have a token
    // reference, we can safely assume that the channel is already enabled.

    pub fn as_enabled(&self, _token: &inventory::Token<DmaEn>) -> &DmaChEn {
        &self.0
    }

    pub fn as_enabled_mut(&mut self, _token: &inventory::Token<DmaEn>) -> &mut DmaChEn {
        &mut self.0
    }

    // If we consume the token, we can assume that the DMA will be enabled
    // infinitely. Or at least until the token will be resurrected.
    pub fn into_enabled(self, token: inventory::Token<DmaEn>) -> DmaChEn {
        // To be recreated in `into_disabled()`.
        drop(token);
        self.0
    }
}

impl DmaChEn {
    // The only way to obtain an instance of `DmaChEn` is with `DmaCh::into_enabled`
    // method. So we can claim that the newly created token is the token dropped in
    // `DmaCh::into_enabled`.
    pub fn into_disabled(self) -> (DmaCh, inventory::Token<DmaEn>) {
        // Restore the token dropped in `into_enabled()`.
        let token = unsafe { inventory::Token::new() };
        (DmaCh(self), token)
    }

    // An example method, which can be called only when DMA channel is enabled.
    fn do_something_with_enabled_dma_ch(&self) {}
}

fn main() {
    // Instantiate the tokens. This is `unsafe` because we can accidentally
    // create more than one instance of a token.
    let dma_reg = unsafe { DmaReg::take() };
    let dma_ch_reg = unsafe { DmaChReg::take() };

    // Instantiate drivers. Only one instance of each driver can exist, because
    // they depend on the tokens.
    let mut dma = Dma::new(dma_reg);
    let mut dma_ch = DmaCh::new(dma_ch_reg);
    // DMA is disabled now.
    assert!(!DMA_EN.load(Ordering::Relaxed));

    // We can call methods defined for disabled `Dma`.
    dma.do_something_with_disabled_dma();
    // We can't call methods defined for enabled `Dma`. This won't compile.
    // dma.do_something_with_enabled_dma();

    {
        // Enable DMA. This method returns a guard scoped to the enclosing block.
        let mut dma = dma.enable();
        assert!(DMA_EN.load(Ordering::Relaxed));

        // We can call methods defined for enabled DMA.
        dma.do_something_with_enabled_dma();
        // Calls to methods defined for disabled DMA won't compile.
        // dma.do_something_with_disabled_dma();

        // Get enabled DMA channel. Type system ensures that the lifetime of
        // `dma_ch` is always shorter than the lifetime of `dma`.
        let dma_ch = dma_ch.as_enabled(dma.inventory_token());
        // We can call methods defined for enabled DMA channel.
        dma_ch.do_something_with_enabled_dma_ch();
    }
    // After exiting the scope above, DMA is automatically disabled.
    assert!(!DMA_EN.load(Ordering::Relaxed));

    // Sometimes we can't use lifetimes to encode resource states. Here is another
    // approach which encodes states in the types.

    // Enable DMA while converting our driver to a different type.
    let mut dma = dma.into_enabled();
    assert!(DMA_EN.load(Ordering::Relaxed));

    // We can call methods defined for enabled types.
    dma.do_something_with_enabled_dma();
    dma_ch
        .as_enabled(dma.inventory_token())
        .do_something_with_enabled_dma_ch();

    // Obtain the owned token from `dma`. From now `dma` has a type that can't be
    // disabled.
    let (dma, token) = dma.share1();
    // Get enabled DMA channel. This method consumes the token.
    let dma_ch = dma_ch.into_enabled(token);
    // We can call methods defined for enabled DMA channel.
    dma_ch.do_something_with_enabled_dma_ch();

    // At this moment DMA can't be disabled. If `dma` is dropped, then the
    // resource will remain enabled. We need to get our token back from `dma_ch`.
    let (dma_ch, token) = dma_ch.into_disabled();
    let dma = dma.merge1(token);
    // Now DMA can be disabled.
    let dma = Dma::from_enabled(dma);
    assert!(!DMA_EN.load(Ordering::Relaxed));
}

Structs

Count0

A token counter.

Count1

A token counter.

Count2

A token counter.

Count3

A token counter.

Count4

A token counter.

Count5

A token counter.

Count6

A token counter.

Count7

A token counter.

Count8

A token counter.

Count9

A token counter.

Count10

A token counter.

Count11

A token counter.

Guard

An RAII scoped guard for the inventory item T. Will call Item::teardown on drop.

GuardToken

A zero-sized token for Item::teardown. Cannot be created by the user.

Inventory

The inventory wrapper for T. Parameter C encodes the number of emitted tokens.

Token

A zero-sized token for resource T. Having an instance or reference to it, guarantees that T is in its active state.

Traits

Item

An inventory item interface.

Type Definitions

Inventory0

Inventory with bounded count.

Inventory1

Inventory with bounded count.

Inventory2

Inventory with bounded count.

Inventory3

Inventory with bounded count.

Inventory4

Inventory with bounded count.

Inventory5

Inventory with bounded count.

Inventory6

Inventory with bounded count.

Inventory7

Inventory with bounded count.

Inventory8

Inventory with bounded count.

Inventory9

Inventory with bounded count.

Inventory10

Inventory with bounded count.

Inventory11

Inventory with bounded count.