Implementing typestate
Marc Schütz via Digitalmars-d
digitalmars-d at puremagic.com
Wed Sep 16 11:01:27 PDT 2015
On Wednesday, 16 September 2015 at 17:15:55 UTC, Ola Fosheim
Grøstad wrote:
> On Wednesday, 16 September 2015 at 17:03:14 UTC, Marc Schütz
> wrote:
>> On Tuesday, 15 September 2015 at 21:44:25 UTC, Freddy wrote:
>>> On Tuesday, 15 September 2015 at 17:45:45 UTC, Freddy wrote:
>>>> Rust style memory management in a library
>>>
>>> Wait nevermind about that part, it's harder than I thought.
>>
>> Yeah, I thought about type-states as a way of implementing
>> borrowing, too. I think the biggest difficulty is that the
>> state of one object (the owner) can be affected by what
>> happens in other objects (i.e., it becomes mutable again when
>> those are destroyed).
>
> If the borrowed reference itself follows move semantics, can't
> you just require it to be swallowed by it's origin as the
> "close" operation?
>
> pseudocode:
>
> File<Open> f = open();
> (File<OpenLending> f, FileRef<Ready> r) = f.borrow();
>
> dostuff(r);
>
> (File<Open> f, FileRef<Void> r) = f.unborrow(r);
>
> File<Closed> f = f.close()
But the `unborrow` is explicit. What I'd want is to use the
implicit destructor call:
struct S {
static struct Ref {
private @typestate alias owner;
private S* p;
@disable this();
this()
typestate(alias owner) {
this.owner := owner; // re-alias operator
this.owner.refcount++;
}
body {
this.p = &owner;
}
this(this) {
this.owner.refcount++;
}
~this() {
this.owner.refcount--;
}
}
@typestate size_t refcount = 0;
S.Ref opUnary(string op : "*")() {
// overload address operator (not yet supported)
return S.Ref(@typestate this);
}
~this() static if(refcount == 0) { }
}
void foo(scope S.Ref p);
void bar(-> S.Ref p); // move
void baz(S.Ref p);
S a; // => S<0>
{
auto p = &a; // => S<1>
foo(p); // pass-by-scope doesn't copy or destroy
// => S<1>
p.~this(); // (implicit) => S<0>
}
{
auto p = &a; // => S<1>
bar(p); // pass-by-move, no copy or destruction
// => S<1>
p.~this(); // (implicit) => S<0>
}
{
auto p = &a; // => S<1>
baz(p); // compiler sees only the copy,
// but no destructor => S<2>
p.~this(); // (implicit) => S<1>
}
a.~this(); // ERROR: a.refcount != 0
The first two cases can be analyzed at the call site. But the
third one is problematic, because inside `baz()`, the compiler
doesn't know where the alias actually points to, because it could
be in an entirely different compilation unit. I guess this can be
solved by disallowing all operations modifying or depending on an
alias type-state.
(Other complicated things, like preserving type-state through
references or array indices, probably shouldn't even be
attempted.)
More information about the Digitalmars-d
mailing list