Expand description
Bindings (Bind
), a static storage with runtime initialization and
configuration-time borrow checking.
Bindings are essentially fancy global variables defined in a kernel
configuration. They are defined by Bind::define
and initialized by
provided closures at runtime. They can be consumed or borrowed by the entry
points of executable kernel objects or the initializers of another
bindings, with a dependency graph explicitly defined by the passing of
binders.
The configuration system tracks the usage of bindings and employs static checks to ensure that the borrowing rules are followed by the users of the bindings. It aborts the compilation if the rules may be violated.
Bindings use hunks (Hunk
) as a storage for their contents. Bindings are
initialized in startup hooks, where CPU Lock is active, and therefore most kernel services
are unavailable.
Relation to Other Specifications: Resources in RTIC 1 serve the similar need with a quite different design. In R3, bindings are defined in modular, encapsulated configuration functions and associated to various kernel objects. The configuration system takes all definitions and figures out the correct initialization order. In RTIC, all resources are defined in one place and initialized by an application-provided
#[init]
function.
Binders
A binder (Binder
) represents a specific borrow mode of a binding. A
configuration function creates a binding by calling one of Bind
’s methods
and uses it in the definition of another object where the binding is intended to
be consumed, i.e., borrowed or moved out by its entry point or initializer. The
type a binder produces is called its materialized form.
use r3::{bind::bind, kernel::StaticTimer, prelude::*};
let count = bind((), || 0).finish(cfg);
// ^^ ^^
// .--' '--------,
// no binders no materialized values
StaticTimer::define()
// n.b. `(x,)` is a one-element tuple
.start_with_bind((count.borrow_mut(),), |count: &mut i32| {
// ^^^^^^^^^^^^^^^^^^ ^^^^^^^^
// | |
// BindBorrowMut<'_, _, i32> &'call mut i32
// gives... for some lifetime 'call
})
.finish(b);
The only way for safe code to make a binding available to runtime code in a
meaningful way is to include a binder in a dependency list (e.g., the binder
parameter of ExecutableDefinerExt::start_with_bind
) as shown above or to
call Bind::as_ref
to create a BindRef
, which is directly consumable
in runtime code. Most binders can’t escape from a configuration function.
The following table lists all provided binders:
Bind:: | Type | Confers | On binding | On executable |
---|---|---|---|---|
borrow | BindBorrow | &'call T | ✓ | ✓ |
borrow_mut | BindBorrowMut | &'call mut T | ✓ | ✓ |
take_ref | BindTakeRef | &'static T | ✓ | ✓ |
take_mut | BindTakeMut | &'static mut T | ✓ | |
take | BindTake | T | ✓ | |
as_ref | BindRef | &'static T | ✓ |
-
The
Bind::
column shows the methods to create the binders. -
The Type column shows the types representing the binders.
-
The Confers column shows the respective materialized forms of the binders. The lifetime
'call
represents the call duration of the consuming function.- For
&'call T
and&'call mut T
, the caller has the freedom of choosing an arbitrary lifetime, so the consuming function must be generic over any lifetimes. In function and closure parameters, reference types without explicit lifetimes are automatically made generic in this way owing to the lifetime elision rules.
- For
-
The On binding column shows which types of binders can be consumed by another binding’s initializer via
BindDefiner::init_with_bind
. -
The On executable column shows which types of binders can be consumed by executable objects, viz., tasks, interrupt handlers, and timers, via
ExecutableDefinerExt::start_with_bind
.- An executable object may execute its entry point for multiple times
throughout its lifetime. For this reason, an executable object is not
allowed to consume
BindTake
(which moves out the value) orBindTakeMut
(which mutably borrows the value indefinitely).
- An executable object may execute its entry point for multiple times
throughout its lifetime. For this reason, an executable object is not
allowed to consume
Initialization Order
The configuration system determines the initialization order of the defined bindings by topological sorting with a preference toward the definition order. The specific algorithm is not a part of the stability guarantee.
Planned Features
The following features are planned and may be implemented in the future:
Structs
- A defined binding.
- A binder that gives
&T
to a bound function. - A binder that gives
&mut T
to a bound function. - The definer (static builder) for
Bind
. - Represents a permission to dereference
BindRef
. - A binder that gives
T
to a bound function. - A binder that gives
&'static mut T
to a bound function. - A binder that gives
&'static T
to a bound function. - Applies a function to a
FnBind
’s output. - Unstable. The runtime representation of
BindBorrow
. - Unstable. The runtime representation of
BindBorrowMut
. - Unstable. The runtime representation of
BindTake
. - Unstable. The runtime representation of
BindTakeMut
. - Unstable. The runtime representation of
BindTakeRef
.
Enums
- Error type for
BindTable::get
.
Constants
- The priority of the startup hooks used to initialize bindings.
Traits
- Represents a binder, which represents a specific way to access the contents of a binding from a runtime function.
- A trait for definer objects (static builders) for kernel objects that can spawn a thread that executes after the execution of all startup hooks is complete.
- An extension trait for
ExecutableDefiner
. Provides a method to attach an entry point with materialized bindings. - Unstable. The runtime representation of
Binder
. - An extension trait for destructing
Bind
<_, (T0, T1, ...)>
into individual bindings(Bind<_, T0>, Bind<_, T1>, ...)
.