Turbo Tasks: Tasks and Functions
A Task is an instance of a function along with its arguments. These tasks are the fundamental units of work within the build system.
Defining Tasks
Tasks are defined using Rust functions annotated with the #[turbo_tasks::function] macro.
#![allow(unused)]
fn main() {
#[turbo_tasks::function]
fn add(a: i32, b: i32) -> Vc<Something> {
// Task implementation goes here...
}
}
- Tasks can be implemented as either a synchronous or asynchronous function.
- Arguments must implement the
TaskInputtrait. Usually these are primitives or types wrapped inVc<T>. For details, see the task inputs documentation. - The external signature of a task always returns
Vc<T>. This can be thought of as a lazily-executed promise to a value of typeT. - Generics (type or lifetime parameters) are not supported in task functions.
External Signature Rewriting
The #[turbo_tasks::function] macro rewrites the arguments and return values of functions. The rewritten function signature is referred to as the “external signature”.
Argument Rewrite Rule
-
Function arguments with the
ResolvedVc<T>type are rewritten toVc<T>.- The value cell is automatically resolved when the function is called. This reduces the work needed to convert between
Vc<T>andResolvedVc<T>types. - This rewrite applies for
ResolvedVc<T>types nested inside ofOption<ResolvedVc<T>>andVec<ResolvedVc<T>>. For more details, refer to theFromTaskInputtrait.
- The value cell is automatically resolved when the function is called. This reduces the work needed to convert between
-
Method arguments of
&selfare rewritten toself: Vc<Self>.
Return Type Rewrite Rules
- A return type of
Result<Vc<T>>is rewritten intoVc<T>.- The
Result<Vc<T>>return type allows for idiomatic use of the?operator inside of task functions.
- The
- A function with no return type is rewritten to return
Vc<()>instead of(). - The
impl Future<Output = Vc<T>>type implicitly returned by an async function is flattened into theVc<T>type, which represents a lazy and re-executable version of theFuture.
Some of this logic is represented by the TaskOutput trait and its associated Return type.
External Signature Example
As an example, the method
#![allow(unused)]
fn main() {
#[turbo_tasks::function]
async fn foo(
&self,
a: i32,
b: Vc<i32>,
c: ResolvedVc<i32>,
d: Option<Vec<ResolvedVc<i32>>>,
) -> Result<Vc<i32>> {
// ...
}
}
will have an external signature of
#![allow(unused)]
fn main() {
fn foo(
self: Vc<Self>, // was: &self
a: i32,
b: Vc<i32>,
c: Vc<i32>, // was: ResolvedVc<i32>
d: Option<Vec<Vc<i32>>>, // was: Option<Vec<ResolvedVc<i32>>>
) -> Vc<i32>; // was: impl Future<Output = Result<Vc<i32>>>
}
Methods with a Self Argument
Tasks can be methods associated with a value or a trait implementation using the arbitrary_self_types nightly compiler feature.
Inherent Implementations
#![allow(unused)]
fn main() {
#[turbo_tasks::value_impl]
impl Something {
#[turbo_tasks::function]
fn method(self: Vc<Self>, a: i32) -> Vc<SomethingElse> {
// Receives the full `Vc<Self>` type, which we must `.await` to get a
// `ReadRef<Self>`.
vdbg!(self.await?.some_field);
// The `Vc` type is useful for calling other methods declared on
// `Vc<Self>`, e.g.:
self.method_resolved(a)
}
#[turbo_tasks::function]
fn method_resolved(self: ResolvedVc<Self>, a: i32) -> Vc<SomethingElse> {
// Same as above, but receives a `ResolvedVc`, which can be `.await`ed
// to a `ReadRef` or dereferenced (implicitly or with `*`) to `Vc`.
vdbg!(self.await?.some_field);
// The `ResolvedVc<Self>` type can be used to call other methods
// declared on `Vc<Self>`, e.g.:
self.method_ref(a)
}
#[turbo_tasks::function]
fn method_ref(&self, a: i32) -> Vc<SomethingElse> {
// As a convenience, receives the fully resolved version of `self`. This
// does not require `.await`ing to read.
//
// It can access fields on the struct/enum and call methods declared on
// `Self`, but it cannot call other methods declared on `Vc<Self>`
// (without cloning the value and re-wrapping it in a `Vc`).
Vc::cell(SomethingElse::new(self.some_field, a))
}
}
}
-
Declaration Location: The methods are defined on
Vc<T>(i.e.Vc<Something>::methodandVc<Something>::method2), not on the inner type. -
&selfSyntactic Sugar: The&selfargument of a#[turbo_tasks::function]implicitly reads the value fromself: Vc<Self>. -
External Signature Rewriting: All of the signature rewrite rules apply here.
selfcan beResolvedVc<T>.asyncandResult<Vc<T>>return types are supported.
Trait Implementations
#![allow(unused)]
fn main() {
#[turbo_tasks::value_impl]
impl Trait for Something {
#[turbo_tasks::function]
fn method(self: Vc<Self>, a: i32) -> Vc<SomethingElse> {
// Trait method implementation...
//
// `self: ResolvedVc<Self>` and `&self` are also valid argument types!
}
}
}
For traits, only the external signature (after rewriting) must align with the trait definition.