Formality model
This section defines a formal model. This is not a proposal for syntax to be added to the Rust language. It allows us to talk uniformly about what is happening. It is intended to cover a superset of functionality we might someday want.
We define an effect E as a set of runtime and associated effects <T as Trait>::Effect:
E = panic
  | loop
  | runtime
  | await
  | throw<T>
  | yield<T>
  | <P0 as Trait<P1...>>::Effect
  | union(E0, ...., En)
Rust "keywords" and funtion declarations can be translated to sets of these primitive effects:
- a const fncan have the effectsunion(panic, loop)
- a fncan have theunion(panic, loop, runtime)(we also call this set of effects "default")
- an async fncan have the effectsunion(await, default)
- a gen fn, if we had that, would have the effectunion(yield<T>, default)
Traits are extended with the ability to define associated effects, declared with do Effect:
#![allow(unused)] fn main() { trait Foo { do Effect; } }
Trait references can bound an associated effect by writing
#![allow(unused)] fn main() { T: Foo<Effect: E> }
which means "the associated effect Effect is a subset of E".
Functions and methods can be declared with a set of effects, written (for now) after the do keyword:
#![allow(unused)] fn main() { fn foo<T: Default>() do runtime, { ... } }
The effect checker enforces:
- if the function body does something that is known not to be const compatible, it must have runtimeeffect;
- when the function calls functions etc., it must have a known superset of their effects declared.
"Marker" effects vs "codegen" effects
In the space of effects we can separate out two categories.
- Marker effects do not require changes to the compiler codegen. They indicate actions that are or are not allowed. Examples include runtime,panic, andloop.
- Codegen effects require changes to the compiler. An example would be awaitorthrow<T>.
Codegen impacts
When a function is compiled that has the await or yield effects, it is compiled using the coroutine transform. The actual function call returns the coroutine.
When a function is compiled that has the throw<T> effect, it is compiled to return a Result, with the result being Ok-wrapped (we can generalize this with the Try trait if we wish).
When a function is called that has the await or yield effects and the containing function has those effects, the result is either "awaited" or "yield*"'d.