Types, Send and Sync

The app attribute injects a context, a collection of variables, into every function. All these variables have predictable, non-anonymous types so you can write plain functions that take them as arguments.

The API reference specifies how these types are generated from the input. You can also generate documentation for you binary crate (cargo doc --bin <name>); in the documentation you'll find Context structs (e.g. init::Context and idle::Context).

The example below shows the different types generates by the app attribute.


# #![allow(unused_variables)]
#fn main() {
//! examples/types.rs

#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]

extern crate panic_semihosting;

use cortex_m_semihosting::debug;
use rtfm::{Exclusive, Instant};

#[rtfm::app(device = lm3s6965)]
const APP: () = {
    static mut SHARED: u32 = 0;

    #[init(schedule = [foo], spawn = [foo])]
    fn init(c: init::Context) {
        let _: Instant = c.start;
        let _: rtfm::Peripherals = c.core;
        let _: lm3s6965::Peripherals = c.device;
        let _: init::Schedule = c.schedule;
        let _: init::Spawn = c.spawn;

        debug::exit(debug::EXIT_SUCCESS);
    }

    #[exception(schedule = [foo], spawn = [foo])]
    fn SVCall(c: SVCall::Context) {
        let _: Instant = c.start;
        let _: SVCall::Schedule = c.schedule;
        let _: SVCall::Spawn = c.spawn;
    }

    #[interrupt(resources = [SHARED], schedule = [foo], spawn = [foo])]
    fn UART0(c: UART0::Context) {
        let _: Instant = c.start;
        let _: resources::SHARED = c.resources.SHARED;
        let _: UART0::Schedule = c.schedule;
        let _: UART0::Spawn = c.spawn;
    }

    #[task(priority = 2, resources = [SHARED], schedule = [foo], spawn = [foo])]
    fn foo(c: foo::Context) {
        let _: Instant = c.scheduled;
        let _: Exclusive<u32> = c.resources.SHARED;
        let _: foo::Resources = c.resources;
        let _: foo::Schedule = c.schedule;
        let _: foo::Spawn = c.spawn;
    }

    extern "C" {
        fn UART1();
    }
};

#}

Send

Send is a marker trait for "types that can be transferred across thread boundaries", according to its definition in core. In the context of RTFM the Send trait is only required where it's possible to transfer a value between tasks that run at different priorities. This occurs in a few places: in message passing, in shared static mut resources and in the initialization of late resources.

The app attribute will enforce that Send is implemented where required so you don't need to worry much about it. It's more important to know where you do not need the Send trait: on types that are transferred between tasks that run at the same priority. This occurs in two places: in message passing and in shared static mut resources.

The example below shows where a type that doesn't implement Send can be used.


# #![allow(unused_variables)]
#fn main() {
//! `examples/not-send.rs`

#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]

extern crate panic_halt;

use core::marker::PhantomData;

use cortex_m_semihosting::debug;
use rtfm::app;

pub struct NotSend {
    _0: PhantomData<*const ()>,
}

#[app(device = lm3s6965)]
const APP: () = {
    static mut SHARED: Option<NotSend> = None;

    #[init(spawn = [baz, quux])]
    fn init(c: init::Context) {
        c.spawn.baz().unwrap();
        c.spawn.quux().unwrap();
    }

    #[task(spawn = [bar])]
    fn foo(c: foo::Context) {
        // scenario 1: message passed to task that runs at the same priority
        c.spawn.bar(NotSend { _0: PhantomData }).ok();
    }

    #[task]
    fn bar(_: bar::Context, _x: NotSend) {
        // scenario 1
    }

    #[task(priority = 2, resources = [SHARED])]
    fn baz(mut c: baz::Context) {
        // scenario 2: resource shared between tasks that run at the same priority
        *c.resources.SHARED = Some(NotSend { _0: PhantomData });
    }

    #[task(priority = 2, resources = [SHARED])]
    fn quux(mut c: quux::Context) {
        // scenario 2
        let _not_send = c.resources.SHARED.take().unwrap();

        debug::exit(debug::EXIT_SUCCESS);
    }

    extern "C" {
        fn UART0();
        fn UART1();
    }
};

#}

It's important to note that late initialization of resources is effectively a send operation where the initial value is sent from idle, which has the lowest priority of 0, to a task with will run with a priority greater than or equal to 1. Thus all late resources need to implement the Send trait.

Sharing a resource with init can be used to implement late initialization, see example below. For that reason, resources shared with init must also implement the Send trait.


# #![allow(unused_variables)]
#fn main() {
//! `examples/shared-with-init.rs`

#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]

extern crate panic_halt;

use cortex_m_semihosting::debug;
use lm3s6965::Interrupt;
use rtfm::app;

pub struct MustBeSend;

#[app(device = lm3s6965)]
const APP: () = {
    static mut SHARED: Option<MustBeSend> = None;

    #[init(resources = [SHARED])]
    fn init(c: init::Context) {
        // this `message` will be sent to task `UART0`
        let message = MustBeSend;
        *c.resources.SHARED = Some(message);

        rtfm::pend(Interrupt::UART0);
    }

    #[interrupt(resources = [SHARED])]
    fn UART0(c: UART0::Context) {
        if let Some(message) = c.resources.SHARED.take() {
            // `message` has been received
            drop(message);

            debug::exit(debug::EXIT_SUCCESS);
        }
    }
};

#}

Sync

Similarly, Sync is a marker trait for "types for which it is safe to share references between threads", according to its definition in core. In the context of RTFM the Sync trait is only required where it's possible for two, or more, tasks that run at different priority to hold a shared reference to a resource. This only occurs with shared static resources.

The app attribute will enforce that Sync is implemented where required but it's important to know where the Sync bound is not required: in static resources shared between tasks that run at the same priority.

The example below shows where a type that doesn't implement Sync can be used.


# #![allow(unused_variables)]
#fn main() {
//! `examples/not-sync.rs`

#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]

extern crate panic_halt;

use core::marker::PhantomData;

use cortex_m_semihosting::debug;

pub struct NotSync {
    _0: PhantomData<*const ()>,
}

#[rtfm::app(device = lm3s6965)]
const APP: () = {
    static SHARED: NotSync = NotSync { _0: PhantomData };

    #[init]
    fn init(_: init::Context) {
        debug::exit(debug::EXIT_SUCCESS);
    }

    #[task(resources = [SHARED])]
    fn foo(c: foo::Context) {
        let _: &NotSync = c.resources.SHARED;
    }

    #[task(resources = [SHARED])]
    fn bar(c: bar::Context) {
        let _: &NotSync = c.resources.SHARED;
    }

    extern "C" {
        fn UART0();
    }
};

#}