Description
Let's try this again.
Summary
Change the declaration syntax of resources from static variables to fields of a
struct.
Current behavior
Today resources are represented in the DSL as static variables. There's a
distinction between static
and static mut
resources. For the former type one
can only get a shared reference (&-
) to the underlying data. For the latter
type one can get a mutable reference( &mut-
) to the data but may need to use a
critical section (lock
API).
There's also distinction between compile time initialized resources (early
resources) and runtime initialized resources (late resources). The distinction
appears in the declaration syntax: late resources have an initial value of ()
(the unit value) in their declaration.
The following snippet shows the 4 possible types of resources.
#[rtfm::app]
const APP: () = {
static A: Early = Early::new();
static B: Late = ();
static mut C: Early = Early::new();
static mut D: Late = ();
// ..
};
Access to resources is controlled using resources
lists. Each context declares
the resources it can access in this list.
Below the continuation of the previous snippet that shows the resources
lists.
#[rtfm::app]
const APP: () = {
// ..
#[task(priority = 1, resources = [A, B, C, D])]
fn a(cx: a::Context) {
let a: &Early = cx.resources.A;
let b: &Late = cx.resources.B;
cx.resources.C.lock(|c: &mut Early| { /* .. */ });
cx.resources.D.lock(|d: &mut Late| { /* .. */ });
}
#[task(priority = 2, resources = [A, B, C, D])]
fn b(cx: b::Context) {
let a: &Early = cx.resources.A;
let b: &Late = cx.resources.B;
let c: &mut Early = cx.resources.C;
let d: &mut Late = cx.resources.D;
}
};
Proposal
Declaration
A structure named Resources
declared within the #[app]
module will be used
to declare all the resources in the application. Each field of this struct
is a resource. There is no longer a static
/ static mut
difference at
declaration site.
The #[init]
attribute can be used to give an initial (compile-time) value to a
resource. Resources that are not given an initial value are considered late
resources and will be initialized to the values returned by the #[init]
function before interrupts (tasks) are re-enabled.
Example from the previous section ported to the new syntax.
#[rtfm::app]
const APP: () = {
struct Resources {
#[init(Early::new())]
a: Early,
b: Late,
#[init(Early::new())]
c: Early,
d: Late,
}
// ..
};
Access lists
The syntax of resources
lists is extended to include shared access: &x
.
The current "by value" syntax will now have the meaning of exclusive access.
Resources with only shared access will inherit the properties, requirements
(Sync
bound) and API of today's static
resources. Resources with only
exclusive access will inherit the properties, requirements and API (lock
) of
today's static mut
resources.
Continuation of the example ported to the new syntax:
#[rtfm::app]
const APP: () = {
// ..
#[task(priority = 1, resources = [&a, &b, c, d])]
fn a(cx: a::Context) {
let a: &Early = cx.resources.a;
let b: &Late = cx.resources.b;
cx.resources.c.lock(|c: &mut Early| { /* .. */ });
cx.resources.d.lock(|d: &mut Late| { /* .. */ });
}
#[task(priority = 2, resources = [&a, &b, c, d])]
fn b(cx: b::Context) {
let a: &Early = cx.resources.a;
let b: &Late = cx.resources.b;
let c: &mut Early = cx.resources.c;
let d: &mut Late = cx.resources.d;
}
};
LateResources
The LateResources
syntax / API is unchanged. Both examples initialize their late resources with the same code, which is shown below:
const APP: () = {
// ..
#[init]
fn init(cx: init::Context) -> init::LateResources {
init::LateResources { b: Late::new(), d: Late::new() }
}
};
Restrictions
A single resource cannot appear in the same list twice under different access
modes:
#[task(resources = [x, &x])]
//~^ error: resource `x` appears more than once in the list
A single resource cannot be accessed in different ways (shared and exclusive)
from different tasks.
#[task(resources = [x])]
fn a(cx: a::Context) { /* .. */ }
#[task(resources = [&x])]
//~^ error: shared and exclusive access to the same resource is not allowed
fn b(cx: b::Context) { /* .. */ }
Motivation
-
The current late resource syntax is strange as it would not normally pass the
type checker in normal Rust. -
The syntax difference between
static
andstatic mut
resources is
artificial, for all resources are lowered down tostatic mut
variables.
Expressing shared access vs exclusive access in theresources
list feels
more natural and lends itself to API extensions like exclusive-shared locks.
Extensions
These are not part of the main proposal and can be discussed as separated RFCs
but are listed here to show the possibilities.
exclusive-shared locks
This syntax lends itself to implementing exclusive-shared locks (AKA
readers-writer locks) as proposed in RFC #129. The required changes would
involve (a) lifting the second restriction so that shared and exclusive access
to a single resource can be mixed, and (b) adding traits for the two new kinds
of locks:
// crate: rtfm-core
trait LockShared {
type Data;
fn lock_shared<R>(&self, f: impl FnOnce(&Self::Data)) -> R;
}
trait LockExclusive: LockShared {
// (or this could be named `lock`)
// (or a second method named `lock` that is an alias of this one could be added)
fn lock_exclusive<R>(&mut self, f: impl FnOnce(&mut Self::Data)) -> R;
}
#[deprecated(note = "use `LockExclusive / lock_exclusive`")]
trait Lock {
type Data;
fn lock<R>(&mut self, f: impl FnOnce(&mut Self::Data)) -> R;
}
Example:
#[rtfm::app]
const APP: () = {
// `Type` must implement the `Sync` trait
struct Resources {
x: Type,
y: AnotherType,
}
#[task(priority = 1, resources = [x, y])]
fn a(cx: a::Context) {
// BASEPRI is modified (ceiling = 3)
cx.resources.x.lock_exclusive(|x: &mut Type| { /* .. */ });
// BASEPRI is *not* modified
cx.resources.x.lock_shared(|x: &Type| { /* .. */ });
// BASEPRI is modified (ceiling = 2)
cx.resources.y.lock_exclusive(|y: &mut AnotherType| { /* .. */ });
}
#[task(priority = 2, resources = [&x, y])]
fn b(cx: b::Context) {
// no lock required at intermediate priority
let x: &Type = cx.resources.x;
let y: &mut AnotherType = cx.resources.y;
}
#[task(priority = 3, resources = [&x])]
fn c(cx: c::Context) {
let x: &Type = cx.resources.x;
}
};