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.)

Reply via email to