digitalmars.dip.ideas - Casting shared off safely
- Richard (Rikki) Andrew Cattermole (109/109) Jan 18 The following proposal exists to document a solution to the
The following proposal exists to document a solution to the
problem of safely casting shared off an object.
However, due to current politics that I oppose, removal of
synchronized statements is expected to be removed from the
language in the first edition and therefore this cannot be
accepted.
First, a new storage class is added which I have dubbed
``onstackonly``.
It has the primary feature of not allowing a given variable to be
copied into another variable with an equal or longer lifetime.
```d
int* example1(onstackonly int* input) => input; // Error
```
It overloads:
```d
class Example2 {
void method() {}
void method() shared {}
void method() onstackonly {}
}
```
The storage classes ``shared`` and ``onstackonly`` conflict, if
both are on a variable ``shared`` wins.
```d
void example3(onstackonly int* input) system {
shared int* temp = cast(shared)input;
// temp is not onstackonly
}
```
As a storage class, ``onstackonly`` is very similar to ``scope``,
however its job isn't escape analysis, and perhaps ``scope``
should fallback to its behaviors if no other escape analysis is
enabled.
It is allowed to implicitly cast to ``scope``.
```d
class Example4 {
void method1() scope {
}
void method2() onstackonly {
}
void method2() scope {
}
}
Example4 e = new Example4;
e.method1(); // ok
e.method2(); // ok, this is onstackonly
```
As a storage class, there is no way to put it on a variable in
`` safe`` code directly.
How you do it, is by using a ``synchronized`` statement.
```d
synchronized(onstackonly self = this) {
}
```
For methods, if it is marked ``synchronized``, it is converted to
``shared``, and it is automatically applied to the this pointer.
```d
void method5() synchronized { // is actually shared not
synchronized
onstackonly typeof(this) self = this; // ok because the this
pointer is onstackonly
}
```
This enables a fairly simple approach that can be standardized to
shared objects.
Giving the following life cycle:
1. Allocation with GC, owning reference. Can initialize.
2. Protected with a mutex (monitor) for inter-thread accessors.
3. Unprotected but still guarded, temporary ownership borrowing
with the lock locked.
```d
class Example6 {
void initMe() {
}
void unknownAccessor() shared {
}
void tempOwn() onstackonly {
}
}
Example6 e1 = new Example6;
e1.initMe();
shared(Example6) e2 = cast(shared)e1;
e2.unknownAccessor();
synchronized(onstackonly Example6 e3 = e2) {
e3.tempOwn();
}
```
Assuming we had custom root classes and D class monitor
decoupled, we could define it as such:
```d
class SharableRootClass : void {
final void opMonitorEnter(scope Sharable sharable) {
// monitor.lock;
}
final void opMonitorExit(scope Sharable sharable) {
// monitor.unlock;
}
shared(This) protect(this This)() trusted =>
cast(shared)this;
shared(This) protect(this This)() onstackonly trusted =>
cast(shared)this;
This consume(this This)() onstackonly system => this;
This consume(this This)() shared system => this;
}
```
By doing the custom root class and D class monitor decoupling, we
can now make the default root class of a given edition not have a
monitor, but still support it for the use cases where it is the
right tool for the job.
Jan 18







Richard (Rikki) Andrew Cattermole <richard cattermole.co.nz>