Hello.
> Will Legacy Les see a side effect when he upgrades to SDK Susie's new async
> library?
That’s the advantage of the idea. Legacy code doesn’t need to do anything.
Let’s assume all PHP code written before PHP9 is code that ran inside
a single coroutine that was implicitly defined.
Let’s say we have a function myFun() with a static variable:
```php
<?php
function myFun(): void {
static $counter = 0;
$counter++;
echo "Counter = {$counter}\n";
}
myFun(); // Counter = 1
myFun(); // Counter = 2
myFun(); // Counter = 3
But if you run this same function in another coroutine…:
```php
function myFun(): void {
static $counter = 0;
$counter++;
echo "counter = {$counter}\n";
}
awaitAll([
spawn(myFun(...)), // Counter = 1
spawn(myFun(...)), // Counter = 1
spawn(myFun(...)), // Counter = 1
]);
```
A programmer working with coroutines will not be able to use static or
global variables to pass state.
They have only two ways to do it:
1. Function parameters
2. use to capture variables in a closure
This makes it impossible to accidentally shoot yourself in the foot. A
developer can still do something silly by explicitly passing objects
between coroutines, but now they are doing it consciously.
Although even here we can go further and create something similar to
ownership transfer of an object. In other words, an explicit semantics
that clearly specifies what to do with the object:
1. Should the object be **moved** between coroutines?
2. Should the object be **cloned**?
3. Is the object a special **shared** object that can be safely passed around?
Such semantics makes coroutines thread-safe.
In other words, by designing a special parameter-passing semantics for
coroutines, we can create a perfect specification for coroutines that
can be run both in another thread and in the current one.
At the same time, the memory model becomes equivalent to Erlang’s
model, where there are no shared objects except for specific special
ones.
Such a change requires adding special **Shared** objects to the
language, which can be safely used across different threads. And
developers will no longer be able to use reference-based variables,
except within something like `SharedBox<T>`.
At the same time, implementing multi-threading support is not required
immediately. But once this capability is added, the language semantics
will already be fully prepared for it.
So props/cons:
1. Coroutines become safe execution containers that cannot
accidentally damage shared memory.
2. Old code requires no changes.
3. New coroutines cannot harm old code. And if parameter-passing
semantics are introduced, they won’t be able to harm it at all. PHP
will forbid a programmer from even trying to pass memory to another
coroutine just like that.
4. The language semantics make it possible to describe fully
thread-safe code, which can be added in the future at any time without
major changes.
**The cost:**
1. a developer must write a bit more code to work with shared objects
between coroutines.
2. In such a memory model, you cannot obtain the result of a
coroutine’s execution twice.
3. And this changes the philosophy of awaiting a coroutine: only one
coroutine can wait for another. However, this limitation has many
positive sides, because it greatly simplifies debugging.
Such a memory model is quite modern and yet not new. It is essentially
supported by Go, Erlang, and other next-generation languages.
Therefore, if PHP’s strategy is to eventually become a language with
parallelism while guaranteeing coroutine safety with respect to shared
memory, then this is the right path.
But I want to warn once again about the price that must be paid from
the developer’s point of view. A developer will no longer be able to
use reference variables or pass objects between coroutines.
---
Ed