Created
November 9, 2021 16:58
-
-
Save run-dlang/d1982a29423b2cb545bc9fa452d94c5e to your computer and use it in GitHub Desktop.
Code shared from run.dlang.io. Run with '-preview=dip1000'
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- app.d | |
import borrowcheck; | |
import std.stdio; | |
import core.lifetime; | |
@safe Owned!int getOwned() | |
{ | |
Owned!int n = makeOwned(123); | |
// can return by move (or NRVO) | |
return move(n); | |
} | |
@safe void receiveOwned(Owned!int n) | |
{ | |
return; | |
} | |
@safe void main() | |
{ | |
Owned!int n = getOwned(); | |
// can access and modify value | |
n.borrow.apply!writeln; | |
n.borrow.apply!((ref n) { n = 456; }); | |
n.borrow.apply!writeln; | |
// can't escape a reference: | |
//int* p = n.borrow.apply!((return ref n) => &n); | |
// can't escape a borrow: | |
static Borrowed!int global; | |
//global = n.borrow; | |
// can't destroy while borrowed | |
{ | |
Borrowed!int b = n.borrow; | |
//destroy(n); // assertion failure | |
} | |
// can pass by move | |
receiveOwned(move(n)); | |
// no double free | |
assert(n == Owned!int.init); | |
} | |
--- borrowcheck.d | |
import core.stdc.stdlib; | |
struct Owned(T) | |
{ | |
private T* ptr; | |
private int borrowCount; | |
// ptr must be allocated with malloc | |
@system this(T* ptr) | |
{ | |
this.ptr = ptr; | |
} | |
~this() | |
{ | |
assert(borrowCount == 0); | |
// ok because borrowCount == 0 guarantees exclusive access | |
() @trusted { free(ptr); }(); | |
} | |
@disable this(ref inout typeof(this) other) inout; | |
} | |
Owned!T makeOwned(T)(T value) | |
{ | |
// ok because malloc has a safe interface | |
void* rawPtr = (() @trusted => malloc(T.sizeof))(); | |
// ok because we only use this memory to hold a T | |
T* ptr = (() @trusted => cast(T*) rawPtr)(); | |
*ptr = value; | |
// ok because ptr is allocated with malloc | |
return (() @trusted => Owned!T(ptr))(); | |
} | |
struct Borrowed(T) | |
{ | |
Owned!T* owner; | |
this(Owned!T* owner) | |
{ | |
this.owner = owner; | |
assert(owner.borrowCount < int.max, "Too many borrows!"); | |
owner.borrowCount++; | |
} | |
~this() | |
{ | |
owner.borrowCount--; | |
} | |
} | |
Borrowed!T borrow(T)(return ref Owned!T owner) | |
{ | |
return Borrowed!T(&owner); | |
} | |
auto apply(alias fun, T)(auto ref Borrowed!T this_) | |
{ | |
// ensure no reference to *ptr can escape from `apply` | |
scope T* ptr = this_.owner.ptr; | |
return fun(*ptr); | |
} |
Yes, it's definitely not ideal. Some kind of syntax sugar for monads, like Scala's for
or Haskell's do
, would help a lot with this sort of thing.
I think your general approach here may be sound (but awkward to use).
However, the implementation does have some problems:
@safe void main()
{
Owned!int n = getOwned();
Borrowed!int b = &n;
Borrowed!int c;
c.owner = &n; // Borrowed.owner should be private.
// There are @safe ways of calling destructors manually which do not set the target to .init afterward.
// So, destructors should explicitly set pointer members to null:
c.__dtor();
destroy!false(n);
// Use after free:
c.apply!writeln;
b.apply!((ref n) { n = 1; });
c.apply!writeln;
// Constructors can be manually called in @safe code, too:
b.__ctor(&n);
c.__ctor(&n);
// No assertions failed in the making of this undefined behavior!
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Just to clarify, I know we could write it like this:
But it's not pretty.