.. _task:

Coroutines / tmc::task
-----------------------------------------------------------------------------------
TMC's coroutine type is ``tmc::task<Result>``. It is a lazy/cold coroutine, and will not start executing until it is ``co_await`` ed, submitted to an executor, or manually resumed.

It is a work item, so it is compatible with the ``tmc::post*()`` family of functions.

It is an awaitable, so it is compatible with the ``tmc::spawn*()`` family of functions.

Task is a linear type. It must be passed around by move, and then ultimately awaited exactly once.

* It cannot be copied.
* It must be moved into any operation that uses it. This includes:

  * ``co_await``
  * ``post*()``
  * ``spawn*()``

* A moved-from task must not be used afterward.
* It will destroy its associated coroutine frame/promise after it completes.
* The result that it produces will be available in the awaiting coroutine's scope.
* It cannot be co_awaited again to produce the same result again.

Rules For Safe Usage of Coroutines
--------------------------------------

Lambda Captures
^^^^^^^^^^^^^^^^^

If you use a lambda task, its capture list must be completely empty (``[]``). This is because the lambda will return immediately and may be destroyed, but the
task's lifetime will be longer and it cannot have a dangling reference to the lambda object.

.. code-block:: cpp

   int a;
   auto r = co_await tmc::spawn_many(
     std::ranges::iota_view(0,5) |
     std::ranges::views::transform(
       // This is undefined behavior
       [&a](int i) -> tmc::task<int> { co_return a + i; }
     )
   );

If you need to capture a value or reference into a coroutine, you must pass it as a parameter.
You can use a lambda wrapper to simplify this. Note that the lambda wrapper is not a coroutine 
(it uses ``return`` rather than ``co_return``) - it just invokes the wrapped coroutine function
and returns the coroutine object.

.. code-block:: cpp

   int a;
   auto r = co_await tmc::spawn_many(
     std::ranges::iota_view(0, 5) |
     std::ranges::views::transform(
       // This is safe, by passing the capture through to a parameter
       [&a](int i) -> tmc::task<int> {
         return [](int& x, int y) -> tmc::task<int> { co_return x + y; }(a, i);
       }
     )
   );

Since coroutines cannot capture, it may be cleaner to adopt the guideline to always use named coroutines:

.. code-block:: cpp

   tmc::task<int> adder(int& x, int y) {
     co_return x + y;
   }

   int a;
   auto r = co_await tmc::spawn_many(
     std::ranges::iota_view(0,5) |
     std::ranges::views::transform(
       // This is safe, by passing the capture through to a parameter
       [&a](int i) -> tmc::task<int>{ return adder(a,i); }
     )
   );

If you need to capture ``this``, then consider making the coroutine a member function.

Lifetimes of Externally Referenced Data
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If a coroutine references some external data, you must ensure that the external data remains in scope
as long as the coroutine is executing. This is most relevant when coroutines reference data that is
present in their caller's scope.

This is true for both synchronous / blocking APIs:

.. code-block:: cpp

   tmc::task<void> handler(int& v) {
     // do something with v
   }

   void func() {
     int a;
     // This is safe. It waits for the handler to complete, so `a` stays in scope.
     tmc::post_waitable(tmc::cpu_executor(), handler(a)).wait();
     // This is not safe. Handler may run at some point in the future, after this function returns.
     tmc::post(tmc::cpu_executor(), handler(a));
   }

And for asynchronous / suspending APIs:

.. code-block:: cpp

   tmc::task<void> handler(int& v) {
     // do something with v
   }

   tmc::task<void> coro() {
     int a;
     // This is safe. It waits for the handler to complete, so `a` stays in scope.
     co_await handler(a);
     // This is not safe. Handler may run at some point in the future, after this coroutine ends.
     tmc::spawn(handler(a)).detach();
   }

Forking APIs are safe as long as they are awaited before the referenced object goes out of scope:

.. code-block:: cpp

   tmc::task<void> handler(int& v) {
     // do something with v
   }

   tmc::task<void> coro() {
     int a;
     // This is safe, as long as we await it before this goes out of scope.
     auto tFork = tmc::spawn(handler(a)).fork();

     // ... do other stuff while the forked task runs in parallel

     // Ensure the forked task has finished.
     co_await std::move(tFork);
   }

API Reference
-----------------------------------------------------------------------------------
.. doxygenstruct:: tmc::task
   :members:
