Skip to content

Instantly share code, notes, and snippets.

@rikkimax
Last active January 21, 2020 14:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rikkimax/4cb2cc8ddcac33c1a9bb20de432f9dea to your computer and use it in GitHub Desktop.
Save rikkimax/4cb2cc8ddcac33c1a9bb20de432f9dea to your computer and use it in GitHub Desktop.
DoublyLinkedList!Something global; // error,
//DoublyLinkedList!Something contains headconst fields and is not marked as such (globals can't be anyway)
//also its null (=void wouldn't work either since then it wouldn't be tied to a stack)
void func() @system {
DoublyLinkedList!Something ll = new DoublyLinkedList!Something(); // ok in @system code but won't allow escaping because of headconst fields.
headconst DoublyLinkedList!Something ll = new DoublyLinkedList!Something(); // ok
ll.add(Something.init);
func2(ll); // ok
func3(ll); // error
global = ll;
}
void func2(headconst DoublyLinkedList!Something ll) {} // could be @safe if you wanted it to be
void func3(DoublyLinkedList!Something) @system {} // error, DoublyLinkedList!T contains headconst fields.
...
final class DoublyLinkedList(T) {
private {
Node nullNode; // can't be escaped because of headconst fields,
//but they are =void so they don't need to be set in constructor.
headconst Node first;
headconst Node last;
}
this() @system {
// the refernces inside of nullNode won't be checked because they are =void and this is @system code
// the reference checks for first and last will be checked however as an invariant of the class so we must set them to something.
first = nullNode; // if this wasn't here, it would error as first would be 'null'
last = nullNode;
}
void add(T value) @safe nothrow {
if (last is nullNode) {
first = new Node(nullNode, nullNode, value); // the value's lifetime is that of 'this' DIP25
last = first;
} else {
last.next = new Node(last, nullNode, value);
last = last.next;
}
}
void contains(T value) @safe nothrow {
headconst Node current = first;
while (current !is nullNode) {
if (current.value == value) {
return true;
}
current = current.next;
}
return false;
}
void remove(T value) @safe nothrow {
headconst Node current = first;
while (current !is nullNode) {
if (current.value == value) {
if (current is first)
first = current.next;
else
current.previous.next = current.next; // ok, doesn't escape the lifetime of 'this' DIP25
if (current is last)
last = current.previous;
else
current.next.previous = current.previous; // no need to do null checks, because its guaranteed to not be null!
// GC will handle auto deallocation
break;
}
current = current.next;
}
}
static struct Node {
headconst Node previous = void, // ok, can only be read or modified in @system/@trusted code.
next = void;
T value;
}
}
Foo global;
Foo func() {
auto foo = scoped!Foo(1, 2, 3);
global = foo; // error, uses alias this which returns via headconst
func2(foo); // ok
/+return+/ headconst Foo bar = func3(foo); // ok
return bar; // error, DIP25 bar outlives foo.
return foo; // error, Scoped!T.get() is treated as return ref, so it can't be escaped
}
void func2(/+return+/ headconst Foo foo) {
foo.method(); // ok, doesn't matter if it was passed in via new Foo(1, 2, 3), or via scoped
}
/+return+/ headconst Foo func3(/+return+/ headconst Foo foo) { // DIP25 pass through checks
return foo;
}
void func4() {
Foo foo = new Foo(1, 2, 3);
func2(foo); // ok, func2 doesn't escape foo
headconst Foo value = func3(foo); // ok, func3 doesn't escape foo and the return value can't escape us
// since value is tied to foo's lifetime of this function body
}
...
template scoped(T) if (is(T == class)) {
Scoped scoped(Args...)(Args args) {
return Scoped(new T(args)); // ugh lets not replicate everything completely
}
struct Scoped {
private T payload;
@disable this(this);
/+return+/ headconst T get() {
return payload;
}
alias get this;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment