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 fn
can have the effectsunion(panic, loop)
- a
fn
can have theunion(panic, loop, runtime)
(we also call this set of effects "default") - an
async fn
can 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
runtime
effect; - 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
await
orthrow<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.