September 16, 2015 Re: Implementing typestate | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Idan Arye | On Wednesday, 16 September 2015 at 10:31:58 UTC, Idan Arye wrote:
> What's wrong with two `open()`s in a row? Each will return a new file handle.
Yes, but if you do it by mistake then you don't get the compiler to check that you call close() on both. I should have written "what if you forget close()". Will the compiler then complain at compile time?
You can't make that happen with just move semantics, you need linear typing so that every resource created are consumed exactly once.
| |||
September 16, 2015 Re: Implementing typestate | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Ola Fosheim Grøstad | On Wednesday, 16 September 2015 at 14:34:05 UTC, Ola Fosheim Grøstad wrote:
> On Wednesday, 16 September 2015 at 10:31:58 UTC, Idan Arye wrote:
>> What's wrong with two `open()`s in a row? Each will return a new file handle.
>
> Yes, but if you do it by mistake then you don't get the compiler to check that you call close() on both. I should have written "what if you forget close()". Will the compiler then complain at compile time?
>
> You can't make that happen with just move semantics, you need linear typing so that every resource created are consumed exactly once.
Move semantics should be enough. We can declare the destructor private, and then any code outside the module that implicitly calls the d'tor when the variable goes out of scope will raise a compilation error. In order to "get rid" of the variable, you'll have to pass ownership to the `close` function, so your code won't try to implicitly call the d'tor.
| |||
September 16, 2015 Re: Implementing typestate | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Idan Arye | On Wednesday, 16 September 2015 at 15:34:40 UTC, Idan Arye wrote:
> Move semantics should be enough. We can declare the destructor private, and then any code outside the module that implicitly calls the d'tor when the variable goes out of scope will raise a compilation error. In order to "get rid" of the variable, you'll have to pass ownership to the `close` function, so your code won't try to implicitly call the d'tor.
Sounds plausible, but does this work in C++ and D? I assume you mean that you "reinterpret_cast" to a different type in the close() function, which is cheating, but ok :).
| |||
September 16, 2015 Re: Implementing typestate | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Ola Fosheim Grøstad | On Wednesday, 16 September 2015 at 15:57:14 UTC, Ola Fosheim Grøstad wrote:
> On Wednesday, 16 September 2015 at 15:34:40 UTC, Idan Arye wrote:
>> Move semantics should be enough. We can declare the destructor private, and then any code outside the module that implicitly calls the d'tor when the variable goes out of scope will raise a compilation error. In order to "get rid" of the variable, you'll have to pass ownership to the `close` function, so your code won't try to implicitly call the d'tor.
>
> Sounds plausible, but does this work in C++ and D? I assume you mean that you "reinterpret_cast" to a different type in the close() function, which is cheating, but ok :).
No need for `reinterpret_cast`. The `close` function is declared in the same module as the `File` struct, so it has access to it's private d'tor.
| |||
September 16, 2015 Re: Implementing typestate | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Idan Arye | On Wednesday, 16 September 2015 at 16:24:49 UTC, Idan Arye wrote:
> No need for `reinterpret_cast`. The `close` function is declared in the same module as the `File` struct, so it has access to it's private d'tor.
True, so it might work for D. Interesting.
| |||
September 16, 2015 Re: Implementing typestate | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Freddy | 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).
| |||
September 16, 2015 Re: Implementing typestate | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Marc Schütz | 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()
| |||
September 16, 2015 Re: Implementing typestate | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Ola Fosheim Grøstad | On Wednesday, 16 September 2015 at 17:15:55 UTC, Ola Fosheim Grøstad wrote:
> dostuff(r);
>
> (File<Open> f, FileRef<Void> r) = f.unborrow(r);
Of course, files are tricky since they can change their state themselves (like IO error). Doing that statically would require some kind of branching mechanism with a try-catch that jumps to a different location where the file type changes to "File<Error>"...
Sounds non-trivial to bolt onto an existing language.
| |||
September 16, 2015 Re: Implementing typestate | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Ola Fosheim Grøstad | 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.)
| |||
September 16, 2015 Re: Implementing typestate | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Marc Schütz | On Wednesday, 16 September 2015 at 18:01:29 UTC, Marc Schütz wrote:
> typestate(alias owner) {
> this.owner := owner; // re-alias operator
> this.owner.refcount++;
> }
I don't think this is possible to establish in the general case. Wouldn't this require a full theorem prover? I think the only way for that to work is to fully unroll all loops and hope that a theorem prover can deal with it. Either that or painstakingly construct a proof manually (Hoare logic).
Like, how can you statically determine if borrowed references stuffed into a queue are all released? To do that you must prove when the queue is empty for borrowed references from a specific object, but it could be interleaved with references to other objects.
| |||
Copyright © 1999-2021 by the D Language Foundation
Permalink
Reply