digitalmars.D - Proposal for design of 'scope' (Was: Re: Opportunities for D)
- H. S. Teoh via Digitalmars-d (5/170) Jul 10 2014 T
- H. S. Teoh via Digitalmars-d (60/83) Jul 10 2014 Yep. In whatever shape or form we finally implement 'scope', it would
- Meta (5/13) Jul 10 2014 They are scoped, but to the lifetime of the GC, which is the
- Jacob Carlborg (5/10) Jul 10 2014 Hmm, why wouldn't that work? The scope where you called "myFunc" is
- H. S. Teoh via Digitalmars-d (7/17) Jul 11 2014 [...]
- Jacob Carlborg (5/7) Jul 13 2014 Wouldn't it be possible to define the scope of a parameter to the
- H. S. Teoh via Digitalmars-d (7/14) Jul 13 2014 [...]
- Jacob Carlborg (4/6) Jul 14 2014 Won't the caller's scope always outlive the callee's?
- H. S. Teoh via Digitalmars-d (15/21) Jul 14 2014 [...]
- Jacob Carlborg (5/15) Jul 15 2014 I was thinking that "arg" would have at least the same lifetime as the
- H. S. Teoh via Digitalmars-d (8/26) Jul 15 2014 [...]
- Jacob Carlborg (4/7) Jul 15 2014 Hmm, I didn't think of that :(
- "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> (168/168) Jul 10 2014 I've been working on a proposal for ownership and borrowing since
- "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> (3/4) Jul 10 2014 That's curious. I actually replied to H. S. Teoh in his new
- John Colvin (2/6) Jul 10 2014 You are in the new thread...
- "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> (6/13) Jul 10 2014 Not in the forum, unfortunately:
- Justin Whear (3/5) Jul 10 2014 This is crazy interesting, thanks for writing it up. Need to digest it ...
- Timon Gehr (4/8) Jul 10 2014 (Proving the safety of code containing manual free requires different
- "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> (19/29) Jul 10 2014 Apologies, then :-)
- "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> (20/38) Jul 10 2014 Thinking more about it:
- deadalnix (7/11) Jul 10 2014 There are unions.
- "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> (9/21) Jul 11 2014 How so? `s2` must not extend after `s1`, because otherwise it
- H. S. Teoh via Digitalmars-d (46/98) Jul 11 2014 Hmm. Seems that you're addressing a somewhat wider scope than what I had
- "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> (103/224) Jul 12 2014 Indeed, but it includes what you're suggesting. For most use
- Meta (4/4) Jul 10 2014 On Thursday, 10 July 2014 at 17:04:24 UTC, H. S. Teoh via
- H. S. Teoh via Digitalmars-d (6/11) Jul 10 2014 I'm working on a DIP for this, actually. Hopefully it will be more
- deadalnix (41/77) Jul 10 2014 I'm bugging around with a similar proposal for a while, but quite
- H. S. Teoh via Digitalmars-d (44/108) Jul 11 2014 Looks like we might need to use explicit lifetimes for this. Unless
- "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> (23/45) Jul 12 2014 I'm not so sure about transitivity either, although I started
- "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> (4/14) Jul 13 2014 I've just done so for mine:
- H. S. Teoh via Digitalmars-d (6/16) Jul 14 2014 Mine is here:
- Jacob Carlborg (13/15) Jul 14 2014 From the DIP:
- simendsjo (2/22) Jul 14 2014 Isn't both 1 and 2 deprecated?
- Jacob Carlborg (7/8) Jul 15 2014 Depends on what you mean by "deprecated". People are keep saying that
- Adam D. Ruppe (12/17) Jul 15 2014 These are actually the same thing: if something is stack
- Jacob Carlborg (9/19) Jul 15 2014 If a class is allocated on the stack, its destructor will be called (at
Moving this to a new thread as suggested by Jacob. On Wed, Jul 09, 2014 at 04:57:01PM -0700, H. S. Teoh via Digitalmars-d wrote:On Wed, Jul 09, 2014 at 03:16:37PM -0700, H. S. Teoh via Digitalmars-d wrote: [...]T -- All problems are easy in retrospect.https://issues.dlang.org/show_bug.cgi?id=13085[...] Hmm, apparently, this is a long-standing known issue: https://issues.dlang.org/show_bug.cgi?id=5270 Judging from this, a big missing piece of the current implementation is the actual enforcement of 'scope'. So here's a first stab at refining (and extending) what 'scope' should be: - 'scope' can be applied to any variable, and is part of its type. Let's call this a "scoped type", and a value of this type a "scoped value". - Every scoped type has an associated lifetime, which is basically the scope in which it is declared. - The lifetime of a scoped variable is PART OF ITS TYPE. - An unscoped variable is regarded to have infinite lifetime. - For function parameters, this lifetime is the scope of the function body. - For local variables, the lifetime is the containing lexical scope where it is declared. - Taking the address of a scoped value returns a scoped pointer, whose lifetime is the lexical scope where the address-of operator is used. - A scoped type can only be assigned to another scoped type of identical or narrower lifetime. Basically, the idea here is that a scoped value can only have its scope narrowed, never expanded. In practice, this means: - If a scoped type is a reference type (class or pointer or ref), it can only be assigned to another scoped type whose associated lifetime is equal or contained within the source value's associated lifetime. - If a scoped type is a value type with indirections, it can only be assigned to an lvalue of the same scoped type (with the same associated lifetime). - If a scoped type is a value type with no indirections, it's freely assignable to a non-scoped lvalue of compatible type. - A function's return type can be scoped (not sure what syntax to use here, since it may clash with scope delegates). - The lifetime of the return value is the containing scope of the function definition -- if it's a module-level function, then its lifetime is infinite. If it's an inner function, then its lifetime is the containing lexical scope of its definition. Example: class C {} void func() { // return type of helper is scope(C) with lifetime up to // the end of func's body. scope(C) helper() { ... } } - Returning a value from a function is considered to be equivalent to assigning the value to a variable of the return type of the function. Thus: class C {} void func() { scope(C) c1; // helper's return type has lifetime == func's body scope(C) helper() { scope(C) c2; if (cond) return c1; // OK, c1's lifetime == func's body else return c2; // ILLEGAL: c2's lifetime < func's body } } - Since a scoped return type has its lifetime as part of its type, the type system ensures that scoped values never escape their lifetime. For example, if we are sneaky and return a pointer to an inner function, the type system will prevent leakage of the scoped value: class C {} auto func() { scope(C) c1; // Return type of sneaky is scope(C) with lifetime = // body of func. scope(C) sneaky() { // This is OK, because c1's lifetime == body of // func, which is compatible with its return type. return c1; } // Aha! we now we have broken scope... or have we? return &sneaky; } void main() { // Let's see. Get a function pointer to a function that // "leaks" a scoped value... auto funcptr = func(); // But this doesn't compile, because the return type of // funcptr() is a scoped variable whose lifetime is // inside the body of func(), but since we're outside of // func here, the lifetime of x doesn't match the // lifetime of funcptr()'s return value, so the // following assignment is rejected as having // incompatible types: auto x = funcptr(); // This will actually work... but it's OK, because we // aren't actually storing the return value of // funcptr(), so the scoped value is actually not leaked // after all. funcptr(); } - Aggregates: - It's turtles all the way down: members of scoped aggregates also have scoped type, with lifetime inherited from the parent aggregate. In other words, the lifetime of the aggregate is transitive to the lifetime of its members. For example: class C {} struct S { C c; int x; } int func(scope S s) { // N.B. lifetime of s is func's body. auto c1 = s.c; // typeof(c1) == scope(C) with lifetime = func's body C d; d = c1; // illegal: c1 has shorter lifetime than d. return s.x; // OK, even though typeof(s.x) has lifetime = // func's body, it's a value type so we're // actually copying it to func's return value, // not returning the actual scoped int. } - Passing parameters: since unscoped values are regarded to have infinite lifetime, it's OK to pass unscoped values into scoped function parameters: it's a narrowing of lifetime of the original value, which is allowed. (What's not allowed is expanding the lifetime of a scoped value.) I'm sure there are plenty of holes in this proposal, so destroy away. ;-) T -- If I were two-faced, would I be wearing this one? -- Abraham Lincoln
Jul 10 2014
On Thu, Jul 10, 2014 at 01:56:39PM +0200, Jacob Carlborg via Digitalmars-d wrote:On 10/07/14 01:57, H. S. Teoh via Digitalmars-d wrote:Done.[...] I'm sure there are plenty of holes in this proposal, so destroy away. ;-)You should post this in a new thread.I'm wondering if a lot more data can be statically allocated. Then passed by reference to functions taking scope parameters. This should be safe since the parameter is guaranteed to outlive the function call.Yep. In whatever shape or form we finally implement 'scope', it would allow us to address such issues. On Thu, Jul 10, 2014 at 01:26:54PM +0000, Wyatt via Digitalmars-d wrote:On Wednesday, 9 July 2014 at 23:58:39 UTC, H. S. Teoh via Digitalmars-d wrote:I think in some cases it can be inferred. For example, taking the address of a local variable should return an appropriately-scoped pointer type, so that the type system can statically verify that it doesn't leak past the variable's lifetime: int* myFunc(int* q) { int x; auto ptr = &x; // typeof(ptr) == scoped(int*) with lifetime == x.lifetime if (cond) return q; // OK, q has infinite lifetime else return ptr; // compile error: cannot convert scoped(int*) to int* } There is an interesting subtlety here, in that local variables themselves are not necessarily scoped, for example: class C {} C createObj() { auto obj = new C; // obj is a local variable return obj; // but its lifetime can be extended outside the function } Yet addresses of local variables are scoped: class C {} C* createObj() { auto obj = new C; return &obj; // INVALID: this would allow the caller // to access a local variable that's // gone out of scope } This leads to some further complications, for example: class C { C* getRef() { return &this; } } C* func(scope C obj) { // In here, obj's lifetime is the body of func // But this violates obj's lifetime: return obj.getRef(); } The problem is, how do we statically prevent this sort of abuse? since the definition of C.getRef may not know anything about func, and so can't possibly return a type whose lifetime is restricted to func, yet that's what's needed to prevent the invalid return of obj.getRef's value outside func.So here's a first stab at refining (and extending) what 'scope' should be:In general, I like it, but can scopedness be inferred? The impression I get from this is we're supposed to manually annotate every scoped everything, which IMO kind of moots the benefits in a broad sense.If it _cannot_ be inferred (even if imperfectly), then I wonder if it doesn't make more sense to invert the proposed default and require annotation when scope restrictions need to be eased.The way I see it, scope is really a way for a function to state that it won't keep a persistent reference to the scoped parameter, and so it's OK to pass in, say, a reference to a temporary that will disappear after the function returns. Defaulting to scope will cause far too much breakage, I think, esp. given that code like the following is probably common in the wild: class C {} C myFunc(C obj) { obj.doSomething(); return obj; // will be rejected if parameters are scoped by default } T -- One disk to rule them all, One disk to find them. One disk to bring them all and in the darkness grind them. In the Land of Redmond where the shadows lie. -- The Silicon Valley Tarot
Jul 10 2014
On Thursday, 10 July 2014 at 18:16:57 UTC, H. S. Teoh via Digitalmars-d wrote:There is an interesting subtlety here, in that local variables themselves are not necessarily scoped, for example: class C {} C createObj() { auto obj = new C; // obj is a local variable return obj; // but its lifetime can be extended outside the function }They are scoped, but to the lifetime of the GC, which is the length of the program. Anything that the GC owns can safely be marked scope(static) or however you would write it.
Jul 10 2014
On 10/07/14 20:15, H. S. Teoh via Digitalmars-d wrote:class C {} C myFunc(C obj) { obj.doSomething(); return obj; // will be rejected if parameters are scoped by default }Hmm, why wouldn't that work? The scope where you called "myFunc" is guaranteed to outlive "myFunc". -- /Jacob Carlborg
Jul 10 2014
On Fri, Jul 11, 2014 at 08:56:10AM +0200, Jacob Carlborg via Digitalmars-d wrote:On 10/07/14 20:15, H. S. Teoh via Digitalmars-d wrote:[...] Because the scope of the parameter 'obj' is defined to be the scope of myFunc only, according to the current proposal. T -- What are you when you run out of Monet? Baroque.class C {} C myFunc(C obj) { obj.doSomething(); return obj; // will be rejected if parameters are scoped by default }Hmm, why wouldn't that work? The scope where you called "myFunc" is guaranteed to outlive "myFunc".
Jul 11 2014
On 2014-07-11 16:29, H. S. Teoh via Digitalmars-d wrote:Because the scope of the parameter 'obj' is defined to be the scope of myFunc only, according to the current proposal.Wouldn't it be possible to define the scope of a parameter to the caller's scope? -- /Jacob Carlborg
Jul 13 2014
On Sun, Jul 13, 2014 at 12:07:58PM +0200, Jacob Carlborg via Digitalmars-d wrote:On 2014-07-11 16:29, H. S. Teoh via Digitalmars-d wrote:[...] We could, but how would that help static analysis within the function's body, since the caller's scope is unknown? T -- Truth, Sir, is a cow which will give [skeptics] no more milk, and so they are gone to milk the bull. -- Sam. JohnsonBecause the scope of the parameter 'obj' is defined to be the scope of myFunc only, according to the current proposal.Wouldn't it be possible to define the scope of a parameter to the caller's scope?
Jul 13 2014
On 13/07/14 16:37, H. S. Teoh via Digitalmars-d wrote:We could, but how would that help static analysis within the function's body, since the caller's scope is unknown?Won't the caller's scope always outlive the callee's? -- /Jacob Carlborg
Jul 14 2014
On Mon, Jul 14, 2014 at 10:41:10AM +0200, Jacob Carlborg via Digitalmars-d wrote:On 13/07/14 16:37, H. S. Teoh via Digitalmars-d wrote:[...] Yes, but since the extent of this scope is unknown from inside the function body, it doesn't easily lend itself nicely to check things like this: int* ptr; void func(scope int* arg) { ptr = arg; // should this be allowed? } If we only know that 'arg' has a longer lifetime than func, but we don't know how long it is, then we don't know if it has the same lifetime as 'ptr', or less. So it doesn't really let us do useful checks. T -- "I suspect the best way to deal with procrastination is to put off the procrastination itself until later. I've been meaning to try this, but haven't gotten around to it yet. " -- swrWe could, but how would that help static analysis within the function's body, since the caller's scope is unknown?Won't the caller's scope always outlive the callee's?
Jul 14 2014
On 15/07/14 01:48, H. S. Teoh via Digitalmars-d wrote:Yes, but since the extent of this scope is unknown from inside the function body, it doesn't easily lend itself nicely to check things like this: int* ptr; void func(scope int* arg) { ptr = arg; // should this be allowed? } If we only know that 'arg' has a longer lifetime than func, but we don't know how long it is, then we don't know if it has the same lifetime as 'ptr', or less. So it doesn't really let us do useful checks.I was thinking that "arg" would have at least the same lifetime as the caller, i.e. the same as "ptr". -- /Jacob Carlborg
Jul 15 2014
On Tue, Jul 15, 2014 at 09:19:34AM +0200, Jacob Carlborg via Digitalmars-d wrote:On 15/07/14 01:48, H. S. Teoh via Digitalmars-d wrote:[...] But what if 'ptr' is declared in a private binary-only module, and only the signature of 'func' is known? Then what should 'scope' mean to the compiler when 'func' is being called from another module? T -- ASCII stupid question, getty stupid ANSI.Yes, but since the extent of this scope is unknown from inside the function body, it doesn't easily lend itself nicely to check things like this: int* ptr; void func(scope int* arg) { ptr = arg; // should this be allowed? } If we only know that 'arg' has a longer lifetime than func, but we don't know how long it is, then we don't know if it has the same lifetime as 'ptr', or less. So it doesn't really let us do useful checks.I was thinking that "arg" would have at least the same lifetime as the caller, i.e. the same as "ptr".
Jul 15 2014
On 2014-07-15 16:58, H. S. Teoh via Digitalmars-d wrote:But what if 'ptr' is declared in a private binary-only module, and only the signature of 'func' is known? Then what should 'scope' mean to the compiler when 'func' is being called from another module?Hmm, I didn't think of that :( -- /Jacob Carlborg
Jul 15 2014
I've been working on a proposal for ownership and borrowing since some time, and I seem to have come to a very similar result as you have. It is not really ready, because I keep discovering weaknesses, and can only work on it in my free time, but I'm glad this topic is finally addressed. I'll write about what I have now: First of all, as you've already stated, scope needs to be a type modifier (currently it's a storage class, I think). This has consequences for the syntax of any parameters it takes, because for type modifiers there need to be type constructors. This means, the `scope(...)` syntax is out. I suggest to use template instantiation syntax instead: `scope!(...)`, which can be freely combined with the type constructor syntax: `scope!lifetime(MyClass)`. Explicit lifetimes are indeed necessary, but dedicated identifiers for them are not. Instead, it can directly refer to symbol of the "owner". Example: int[100] buffer; scope!buffer(int[]) slice; Instead of lifetime intersections with `&` (I believe Timon proposed that in the original thread), simply specify multiple "owners": `scope!(a, b)`. This works, because as far as I can see there is no need for lifetime unions, only intersections. A problem that has been discussed in a few places is safely returning a slice or a reference to an input parameter. This can be solved nicely: scope!haystack(string) findSubstring( scope string haystack, scope string needle ); Inside `findSubstring`, the compiler can make sure that no references to `haystack` or `needle` can be escape (an unqualified `scope` can be used here, no need to specify an "owner"), but it will allow returning a slice from it, because the signature says: "The return value will not live longer than the parameter `haystack`." // fixed-size arrays (new syntax of Kenji's PR) string[$] text = "Old McDonald had a farm."; auto sub = findSubstring(text, "had"); // typeof(sub) is scope!text(string), // `haystack` gets substituted by `text` assert(sub == "had a farm".); Have multiple parameters? No problem: scope!(a,b)(string) selectOneAtRandom( scope string a, scope string b ); // => a _and_ b will outlive return value For methods, `scope!this` can be used to. It's really no different from other parameters, as `this` is just a special implicit parameter. There is also a nice extension: `scope!(const owner)`. This means, that as long as the value designated as such live, `owner` will be treated as const. An interesting application is the old `byLine` problem, where the function keeps an internal buffer which is reused for every line that is read, but a slice into it is returned. When a user naively stores these slices in an array, she will find that all of them have the same content, because they point to the same buffer. See how this is avoided with `scope!(const ...)`: struct ByLineImpl(Char, Terminator) { private: Char[] line; // ... public: // - return value must not outlive `this` (i.e. the range) // - as long as the return value exists, `this` will be const property scope!(const this)(Char[]) front() const { return line; } void popFront() { // not `const`, of course // ... } // ... } void main() { alias Line = const(char)[]; auto byline = stdin.byLine(); foreach(line; byline) { write(line); // OK, `write` takes its parameters as scope // (assuming the widespread usage of scope throughtout Phobos) } Line[] lines; foreach(line; byline) { lines ~= line; // ERROR: `line` has type scope!(const byline)(Line), not Line } // let's try to work around it: scope!(const byline)(Line)[] clines; foreach(line; byline) { // ERROR: `byline` is const clines ~= line; } // => nope, won't work // another example, to show how it works: auto tmp = byline.front; // OK // `byline` is const as long as `tmp` exists write(byline.front); // OK, `front` is const byline.popFront(); // ERROR: `byline` is const } Describing what happens here: As long as any variable (or temporary) with the type `scope!(const byline)` exists, `byline` itself will be treated as const. "Exists" in this case only referes to lexical scope: A variable is said to "exist" from the point it is declared, to the end of the scope it's declared in. Loops, gotos, and exceptions don't have an effect. This means that it can be easily checked by the compiler, without it having to perform complicated control flow analysis. I also thought about allowing `scope!return` for functions, to specify that it a value will not outlive the value returned from the function, but I'm not sure whether there is an actual use case, and the semantics are not clear. An open question is whether there needs to be an explicit designation of GC'd values (for example by `scope!static` or `scope!GC`), to say that a given values lives as long as it's needed (or "forever"). Specifying an owner in the type also integrates naturally with allocators. Assuming an allocator releases all of it's memory to operating system when it is destroyed, there needs to be a guarantee that none of its contents is referenced anymore at this point. This can be achieved by returning a borrowed reference: struct MyAllocator { scope!this(T) alloc(T)() if(T == class) { // ... } } Note that this does not preclude the allocator from doing garbage collection while it exists; in this manner, `scope!GC` might just be an application of this pattern instead of a special syntax. Now, for the problems: Obviously, there is quite a bit of complexity involved. I can imagine that inferring the scope for templates (which is essential, just as for const and the other type modifiers) can be complicated. On the upside, at least it requires no control or data flow analysis. It's also a purely additive change: If implemented right, no currently working code will break. Then I encountered the following problem, and there are several different variations of it: struct S { int* p; void releaseBuffer() scope { // `scope` in the signature applies to `this` free(this.p); this.p = null; } } int bar(scope ref S a, scope int* b) { a.releaseBuffer(); return *b; // use after free } S s; bar(s, s.p); The root cause of the problem here is the call to `free()`. I _believe_ the solution is that `free()` (and equivalent functions of allocators as well as `delete`) must not accept scope parameters. More realistic candidates for such situations are destructors in combination with move semantics. Therefore, `~this()` needs to be marked as scope, too, for it to be callable on a borrowed object. If a scope object has a non-scope destructor, but no scope one, and is going to be destroyed, this needs to be a compile error. (Rust avoids that problem by making any object const while there are borrowed references, but this requires its complex borrow checker, which we should avoid for D.) I also have a few ideas about owned types and move semantics, but this is mostly independent from borrowing (although, of course, it integrates nicely with it). So, that's it, for now. Sorry for the long text. Thoughts?
Jul 10 2014
On Thursday, 10 July 2014 at 20:10:38 UTC, Marc Schütz wrote:snipThat's curious. I actually replied to H. S. Teoh in his new thread, but somehow it ended up here...
Jul 10 2014
On Thursday, 10 July 2014 at 20:13:18 UTC, Marc Schütz wrote:On Thursday, 10 July 2014 at 20:10:38 UTC, Marc Schütz wrote:You are in the new thread...snipThat's curious. I actually replied to H. S. Teoh in his new thread, but somehow it ended up here...
Jul 10 2014
On Thursday, 10 July 2014 at 20:39:45 UTC, John Colvin wrote:On Thursday, 10 July 2014 at 20:13:18 UTC, Marc Schütz wrote:Not in the forum, unfortunately: http://forum.dlang.org/group/digitalmars.D The two threads named "Proposal for design of 'scope'" have no replies, and my post appears in the original thread: http://forum.dlang.org/thread/lphnen$1ml7$1 digitalmars.com?page=16#post-dvtohzrxoyzhhegzkfqf:40forum.dlang.orgOn Thursday, 10 July 2014 at 20:10:38 UTC, Marc Schütz wrote:You are in the new thread...snipThat's curious. I actually replied to H. S. Teoh in his new thread, but somehow it ended up here...
Jul 10 2014
On Thu, 10 Jul 2014 20:10:36 +0000, Marc Schütz wrote:So, that's it, for now. Sorry for the long text. Thoughts?This is crazy interesting, thanks for writing it up. Need to digest it a bit before offering more feedback.
Jul 10 2014
On 07/10/2014 10:10 PM, "Marc Schütz" <schuetzm gmx.net>" wrote:... Instead of lifetime intersections with `&` (I believe Timon proposed that in the original thread)I didn't. I suggested that _some_ suitable syntax might need to be found. :)The root cause of the problem here is the call to `free()`.(Proving the safety of code containing manual free requires different machinery.)
Jul 10 2014
On Thursday, 10 July 2014 at 21:01:23 UTC, Timon Gehr wrote:On 07/10/2014 10:10 PM, "Marc Schütz" <schuetzm gmx.net>" wrote:Apologies, then :-) But the point still stands - I don't think we need to introduce "invented" lifetime names as Rust does, we can refer to existing identifiers/symbols instead.... Instead of lifetime intersections with `&` (I believe Timon proposed that in the original thread)I didn't. I suggested that _some_ suitable syntax might need to be found. :)I think that once manual memory management is involved, it's impossible to make safety guarantees in the general case. But I also believe that not all is lost: Much can be won when the _implementers_ of libraries take special care (in our case by not marking free() as scope), so that library _users_ cannot accidentally run into these problems. And because the trouble-makers can be clearly identified, there is even a simple rule that covers probably 99% of the cases: "Don't make memory deallocators take borrowed pointers." The reason I'm not completely sure about it is that move semantics can have the same effect as manual memory management: It's possible to move a unique object into a lower scope, which is then destroyed at the end of that scope. It's just much less obvious...The root cause of the problem here is the call to `free()`.(Proving the safety of code containing manual free requires different machinery.)
Jul 10 2014
On Thursday, 10 July 2014 at 20:10:38 UTC, Marc Schütz wrote:struct S { int* p; void releaseBuffer() scope { // `scope` in the signature applies to `this` free(this.p); this.p = null; } } int bar(scope ref S a, scope int* b) { a.releaseBuffer(); return *b; // use after free } S s; bar(s, s.p); The root cause of the problem here is the call to `free()`. I _believe_ the solution is that `free()` (and equivalent functions of allocators as well as `delete`) must not accept scope parameters.Thinking more about it: struct S { int* p; void releaseBuffer() scope { free(this.p); this.p = null; } } int bar(void delegate() a, scope int* b) { a(); return *b; // use after free } S s; bar({ s.releaseBuffer(); }, s.p); So, for what I suggested (`free()` mustn't accept scope) to work, an additional rule is required: While a borrowed reference exist, the original must also be treated as scope. Now, this is much more complicated to implement :-( Maybe there's a better way?
Jul 10 2014
On Thursday, 10 July 2014 at 20:10:38 UTC, Marc Schütz wrote:Instead of lifetime intersections with `&` (I believe Timon proposed that in the original thread), simply specify multiple "owners": `scope!(a, b)`. This works, because as far as I can see there is no need for lifetime unions, only intersections.There are unions. class A { scope!s1(A) a; } scope!s2(A) b; b.a; // <= this has union lifetime of s1 and s2.
Jul 10 2014
On Friday, 11 July 2014 at 06:49:26 UTC, deadalnix wrote:On Thursday, 10 July 2014 at 20:10:38 UTC, Marc Schütz wrote:How so? `s2` must not extend after `s1`, because otherwise it would be illegal to store a `scope!s1` value in `scope!s2`. From the other side, `s1` must not start after `s2`. This means that the lifetime of `b.a` is `s1`, just as it has been annotated, no matter what the lifetime of `b` is. In fact, because `s1` can be longer than `s2`, a copy of `a.b` may safely be kept around after `b` is deleted (but of course not longer than `s1`).Instead of lifetime intersections with `&` (I believe Timon proposed that in the original thread), simply specify multiple "owners": `scope!(a, b)`. This works, because as far as I can see there is no need for lifetime unions, only intersections.There are unions. class A { scope!s1(A) a; } scope!s2(A) b; b.a; // <= this has union lifetime of s1 and s2.
Jul 11 2014
On Thu, Jul 10, 2014 at 08:10:36PM +0000, via Digitalmars-d wrote:I've been working on a proposal for ownership and borrowing since some time, and I seem to have come to a very similar result as you have. It is not really ready, because I keep discovering weaknesses, and can only work on it in my free time, but I'm glad this topic is finally addressed. I'll write about what I have now: First of all, as you've already stated, scope needs to be a type modifier (currently it's a storage class, I think). This has consequences for the syntax of any parameters it takes, because for type modifiers there need to be type constructors. This means, the `scope(...)` syntax is out. I suggest to use template instantiation syntax instead: `scope!(...)`, which can be freely combined with the type constructor syntax: `scope!lifetime(MyClass)`. Explicit lifetimes are indeed necessary, but dedicated identifiers for them are not. Instead, it can directly refer to symbol of the "owner". Example: int[100] buffer; scope!buffer(int[]) slice;Hmm. Seems that you're addressing a somewhat wider scope than what I had in mind. I was thinking mainly of 'scope' as "does not escape the body of this block", but you're talking about a more general case of being able to specify explicit lifetimes. [...]A problem that has been discussed in a few places is safely returning a slice or a reference to an input parameter. This can be solved nicely: scope!haystack(string) findSubstring( scope string haystack, scope string needle ); Inside `findSubstring`, the compiler can make sure that no references to `haystack` or `needle` can be escape (an unqualified `scope` can be used here, no need to specify an "owner"), but it will allow returning a slice from it, because the signature says: "The return value will not live longer than the parameter `haystack`."This does seem to be quite a compelling argument for explicit scopes. It does make it more complex to implement, though. [...]An interesting application is the old `byLine` problem, where the function keeps an internal buffer which is reused for every line that is read, but a slice into it is returned. When a user naively stores these slices in an array, she will find that all of them have the same content, because they point to the same buffer. See how this is avoided with `scope!(const ...)`:This seems to be something else now. I'll have to think about this a bit more, but my preliminary thought is that this adds yet another level of complexity to 'scope', which is not necessarily a bad thing, but we might want to start out with something simpler first. [...]An open question is whether there needs to be an explicit designation of GC'd values (for example by `scope!static` or `scope!GC`), to say that a given values lives as long as it's needed (or "forever").Shouldn't unqualified values already serve this purpose? [...]Now, for the problems: Obviously, there is quite a bit of complexity involved. I can imagine that inferring the scope for templates (which is essential, just as for const and the other type modifiers) can be complicated.I'm thinking of aiming for a design where the compiler can infer all lifetimes automatically, and the user doesn't have to. I'm not sure if this is possible, but based on what Walter said, it would be best if we infer as much as possible, since users are lazy and are unlikely to be thrilled at the idea of having to write additional annotations on their types. My original proposal was aimed at this, that's why I didn't put in explicit lifetimes. I was hoping to find a way to define things such that the lifetime is unambiguous from the context in which 'scope' is used, so that users don't ever have to write anything more than that. This also makes the compiler's life easier, since we don't have to keep track of who owns what, and can just compute the lifetime from the surrounding context. This may require sacrificing some precision in lifetimes, but if it helps simplify things while still giving adequate functionality, I think it's a good compromise. [...]I also have a few ideas about owned types and move semantics, but this is mostly independent from borrowing (although, of course, it integrates nicely with it). So, that's it, for now. Sorry for the long text. Thoughts?It seems that you're the full borrowed reference/pointer problem, which is something necessary. But I was thinking more in terms of the baseline functionality -- what is the simplest design for 'scope' that still gives useful semantics that covers most of the cases? I know there are some tricky corner cases, but I'm wondering if we can somehow find an easy solution for the easy parts (presumably the more common parts), while still allowing for a way to deal with the hard parts. At least for now, I'm thinking in the direction of finding something with simple semantics that, at the same time, produces complex (interesting) effects when composed, that we can use to solve the borrowed pointer problem. T -- Computers are like a jungle: they have monitor lizards, rams, mice, c-moss, binary trees... and bugs.
Jul 11 2014
On Friday, 11 July 2014 at 21:04:05 UTC, H. S. Teoh via Digitalmars-d wrote:On Thu, Jul 10, 2014 at 08:10:36PM +0000, via Digitalmars-d wrote: Hmm. Seems that you're addressing a somewhat wider scope than what I had in mind. I was thinking mainly of 'scope' as "does not escape the body of this block", but you're talking about a more general case of being able to specify explicit lifetimes.Indeed, but it includes what you're suggesting. For most use cases, just `scope` without an explicit lifetime annotation is fully sufficient.[...]It's definitely an extension and not as urgently necessary, although it fits well into the general topic of borrowing: `scope` by itself provides mutable borrowing, but `scope!(const ...)` provides const borrowing, in the sense that another object temporarily takes ownership of the value, so that the original owner can only read the object until it is "returned" by the borrowed value going out of scope. I mentioned it here because it seemed to be an easy extension that could solve an interesting long-standing problem for which we only have workarounds today (`byLineCopy` IIRC). And I have to add that it's not completely thought out yet. For example, might it make sense to have `scope!(immutable ...)`, `scope!(shared ...)`, and if yes, what would they mean...A problem that has been discussed in a few places is safely returning a slice or a reference to an input parameter. This can be solved nicely: scope!haystack(string) findSubstring( scope string haystack, scope string needle ); Inside `findSubstring`, the compiler can make sure that no references to `haystack` or `needle` can be escape (an unqualified `scope` can be used here, no need to specify an "owner"), but it will allow returning a slice from it, because the signature says: "The return value will not live longer than the parameter `haystack`."This does seem to be quite a compelling argument for explicit scopes. It does make it more complex to implement, though. [...]An interesting application is the old `byLine` problem, where the function keeps an internal buffer which is reused for every line that is read, but a slice into it is returned. When a user naively stores these slices in an array, she will find that all of them have the same content, because they point to the same buffer. See how this is avoided with `scope!(const ...)`:This seems to be something else now. I'll have to think about this a bit more, but my preliminary thought is that this adds yet another level of complexity to 'scope', which is not necessarily a bad thing, but we might want to start out with something simpler first.[...]Likely yes. It might however be useful to contemplate, especially with regards to allocators.An open question is whether there needs to be an explicit designation of GC'd values (for example by `scope!static` or `scope!GC`), to say that a given values lives as long as it's needed (or "forever").Shouldn't unqualified values already serve this purpose?[...]I agree. It's already getting ugly with `const pure nothrow safe nogc`, adding another annotation should not be done lightheartedly. However, if the compiler could infer all the lifetimes (which I'm quite sure isn't possible, see the haystack-needle example), I don't see why we'd need `scope` at all. It would at most be a way not to break backward compatibility, but that would be another case where you could say that D has it backwards, like un- safe by default...Now, for the problems: Obviously, there is quite a bit of complexity involved. I can imagine that inferring the scope for templates (which is essential, just as for const and the other type modifiers) can be complicated.I'm thinking of aiming for a design where the compiler can infer all lifetimes automatically, and the user doesn't have to. I'm not sure if this is possible, but based on what Walter said, it would be best if we infer as much as possible, since users are lazy and are unlikely to be thrilled at the idea of having to write additional annotations on their types.My original proposal was aimed at this, that's why I didn't put in explicit lifetimes. I was hoping to find a way to define things such that the lifetime is unambiguous from the context in which 'scope' is used, so that users don't ever have to write anything more than that. This also makes the compiler's life easier, since we don't have to keep track of who owns what, and can just compute the lifetime from the surrounding context. This may require sacrificing some precision in lifetimes, but if it helps simplify things while still giving adequate functionality, I think it's a good compromise.I agree it looks a bit intimidating at first glance, but as far as I can tell it should be relatively straightforward to implement. I'll explain how I think it could be done: The obvious things: The parser needs to recognize the new syntax, and scope needs to be turned into a type modifier and stored in the internal data structures accordingly. It is then possible to define a hierarchy of lifetimes. At the top are global and static variables and the GC heap (`scope!static` or just unannotated), then the come function parameters, then local variables in function bodies, and finally local variables in lower scopes like `if` blocks. This is purely based on lexical scope and order of declaration (local variables are destroyed in inverse order of construction, for example); it can be derived from the AST. Furthermore, it is a strict hierarchy; lifetimes higher in the hierarchy are strict super sets of lower lifetimes. A variables effective lifetime is then its place in this hierarchy, or the lifetime of its owner if one is specified. Once that's done, the semantic phase needs to be extended to check for scope correctness. This seems complicated, but actually needs to touch only a few places. Any time a scope value is copied, by assignment, returning from a function, passing to a function, throwing, and what else I may have missed, the compiler needs to check that the destination's effective lifetime is not wider than that of the source. For function calls, an additional step is necessary, but it isn't really complicated either. Let's take `findSubstring` as an example: scope!haystack(string) findSubstring( scope string haystack, scope string needle ); void foo() { string[$] h = "Hello, world!"; auto found = findSubstring(h, ", "); // `typeof(found)` is now `scope!h` } As owners in function signatures may refer to other parameters (or `this`), the compiler needs to match up these parameters with what is passed in, and substitute them accordingly for type deduction (only for `auto` return values). And that's it, AFAICS. Notice that none of this requires flow control analysis or inter-procedural things, it can all be decided locally at the place of assignment/calling/etc.[...]I already wrote this in a reply to Walter. I believe in some cases we can allow automatic borrowing without any annotation at all, not even bare `scope`. The most obvious examples are pure functions with signatures that guarantee that nothing can be escaped from them: void foo(int[] p) pure; // obvious, function has no opportunity // to keep a reference to `p` int bar(int[] p) pure; // returns an `int` but that's a value // type, and that's ok int[] baz(const(int)[] p) pure; // the return type is not `const` and thus // cannot come from `p` Maybe there are some cases with non-pure functions, too. But on the other hand, I also think that in the end we won't get around introducing explicit annotations, because the above rules can never cover enough cases to disregard the remaining ones. Anyway, I don't believe that explicit annotations will be needed often enough to turn the users away. It will be mostly library writers who have to use them, and Phobos can set a good example there and work out a good style, just as it has done for other matters. It also helps to take a glance at Rust's standard library, to see how frequent or infrequent lifetime annotations will be. They keep popping up here and there, but they are not littered all over the source code. They're frequent enough to confirm my suspicion that they cannot be disregarded, but they're also infrequent enough not to be an annoyance. (I only looked at a few modules, though.)I also have a few ideas about owned types and move semantics, but this is mostly independent from borrowing (although, of course, it integrates nicely with it). So, that's it, for now. Sorry for the long text. Thoughts?It seems that you're the full borrowed reference/pointer problem, which is something necessary. But I was thinking more in terms of the baseline functionality -- what is the simplest design for 'scope' that still gives useful semantics that covers most of the cases? I know there are some tricky corner cases, but I'm wondering if we can somehow find an easy solution for the easy parts (presumably the more common parts), while still allowing for a way to deal with the hard parts. At least for now, I'm thinking in the direction of finding something with simple semantics that, at the same time, produces complex (interesting) effects when composed, that we can use to solve the borrowed pointer problem.
Jul 12 2014
On Thursday, 10 July 2014 at 17:04:24 UTC, H. S. Teoh via Digitalmars-d wrote: Can you copy and paste the text from your original post? It's difficult to read on the web interface.
Jul 10 2014
On Fri, Jul 11, 2014 at 01:01:20AM +0000, Meta via Digitalmars-d wrote:On Thursday, 10 July 2014 at 17:04:24 UTC, H. S. Teoh via Digitalmars-d wrote: Can you copy and paste the text from your original post? It's difficult to read on the web interface.I'm working on a DIP for this, actually. Hopefully it will be more fleshed out than what I posted. I'll post the link when it's ready. T -- The problem with the world is that everybody else is stupid.
Jul 10 2014
I'm bugging around with a similar proposal for a while, but quite fail to put the last pieces in place. It goes similar in On Thursday, 10 July 2014 at 17:04:24 UTC, H. S. Teoh via Digitalmars-d wrote:- For function parameters, this lifetime is the scope of the function body.Some kind of inout scope seem less limiting. The caller know the scope, the callee know that is is greater than itself. It is important as local variable in the outer scope of the function have more restricted scope and must not be assignable. Each parameter have a DIFFERENT lifetime, but it is impossible to tell which one is larger from the callee perspective. Thus you must have a more complex lifetime definition than grater/smaller lifetime. Yup, when you get into the details, quantum effects start to arise.So it is not unscoped, but I'm simply nitpicking on that one.- An unscoped variable is regarded to have infinite lifetime.This get quite tricky to define when you can have both this and a context pointer. Once again, you get into a situation where you have 2 non sortable lifetime to handle. And worse, you'll be creating values out of that mess :)- Since a scoped return type has its lifetime as part of its type, the type system ensures that scoped values never escape their lifetime. For example, if we are sneaky and return a pointer to an inner function, the type system will prevent leakage of theYes rule for access is transitivity. But the rule to write is "antitransitive". It gets tricky when you consider that a member variable may have to be able to "extend" the lifetime of one of its member. IE a member of lifetime B in a value of lifetime A sees it lifetime becoming max(A, B). Considering lifetime aren't always sortable (as show in 2 examples), this is tricky. This basically means that you have to define what happen for non sortable lifetime, and what happen for union/intersection of lifetime. As you see, I've banged my head quite a lot on that one. I'm fairly confident that this is solvable, but definitively require a lot of effort to iron out all the details.- Aggregates: - It's turtles all the way down: members of scoped aggregates also have scoped type, with lifetime inherited from the parent aggregate. In other words, the lifetime of the aggregate is transitive to the lifetime of its members.Get rid of the whole concept of unscopped, and you get rid of a whole class of redundant definition that needs to be done.- Passing parameters: since unscoped values are regarded to have infinite lifetime, it's OK to pass unscoped values into scoped function parameters: it's a narrowing of lifetime of the original value, which is allowed. (What's not allowed is expanding the lifetime of a scoped value.)Need some more iron. But I'm happy to see that some people came up with proposal that are close to what I had in mind. The above mentioned detail may seems scary, but I'm confident these will only cause problems in a small variety of cases. An aspect of the proposal that isn't mentioned is postblit and destructions. scoping will need to redefine theses. Ultimately I love the idea and think D should go in that direction at some point. But now I'd prefers see things ironed out in general in D ( safe is a good example).I'm sure there are plenty of holes in this proposal, so destroy away. ;-)
Jul 10 2014
On Fri, Jul 11, 2014 at 06:41:47AM +0000, deadalnix via Digitalmars-d wrote: [...]On Thursday, 10 July 2014 at 17:04:24 UTC, H. S. Teoh via Digitalmars-d wrote:Looks like we might need to use explicit lifetimes for this. Unless there's a way to simplify it -- i.e., we don't always need exact lifetimes, as long as the estimated lifetime is never larger than the actual lifetime. From the perspective of the callee, for example, if the lifetimes of both parameters are longer than it can see (i.e., longer than the lifetimes of its parent lexical scopes) then it doesn't matter what the exact lifetimes are, it can be treated as an unknown value with a lower bound, as long as it never tries to assign anything with lifetime >= that lower bound. The caller already knows what these lifetimes are from outside, but the function may not need to know. At least, I'm hoping this kind of simplifications will still allow us to do what we need, while reducing complexity.- For function parameters, this lifetime is the scope of the function body.Some kind of inout scope seem less limiting. The caller know the scope, the callee know that is is greater than itself. It is important as local variable in the outer scope of the function have more restricted scope and must not be assignable. Each parameter have a DIFFERENT lifetime, but it is impossible to tell which one is larger from the callee perspective. Thus you must have a more complex lifetime definition than grater/smaller lifetime. Yup, when you get into the details, quantum effects start to arise.Well, yes, the reason I wrote that line was to make the definition uniform across both scoped and unscoped types. :)So it is not unscoped, but I'm simply nitpicking on that one.- An unscoped variable is regarded to have infinite lifetime.Is it possible to simplify this by taking the minimum of the two lifetimes (i.e. intersection)? Or will that run into unsolvable cases?This get quite tricky to define when you can have both this and a context pointer. Once again, you get into a situation where you have 2 non sortable lifetime to handle. And worse, you'll be creating values out of that mess :)- Since a scoped return type has its lifetime as part of its type, the type system ensures that scoped values never escape their lifetime. For example, if we are sneaky and return a pointer to an inner function, the type system will prevent leakage of theAlong these lines, I'm wondering if "turtles all the way down" is the wrong way of looking at it. Consider, for example, an n-level deep nesting of aggregates. If obj.nest1 is const, then obj.nest1.nest2.x must also be const, because otherwise we break the const system. So const is transitive downwards. But if obj.nest1 is a scoped reference type with lifetime L1, that doesn't necessarily mean obj.nest1.y only has lifetime L1. It may be a pointer that points to an infinite lifetime object, for example, so it's not a problem that the pointer goes out of scope before the object pointed to. OTOH, if obj.nest1 has scope L1, then obj itself cannot have a longer lifetime than L1, otherwise we may access obj.nest1 after its lifetime is over. So the lifetime of obj.nest1 must propagate *upwards* (or outwards). This means that scope is transitive outwards, which is the opposite of const/immutable, which are transitive inwards! So it's not turtles all the way down, but "pigeons all the way up". :-PYes rule for access is transitivity. But the rule to write is "antitransitive". It gets tricky when you consider that a member variable may have to be able to "extend" the lifetime of one of its member. IE a member of lifetime B in a value of lifetime A sees it lifetime becoming max(A, B). Considering lifetime aren't always sortable (as show in 2 examples), this is tricky. This basically means that you have to define what happen for non sortable lifetime, and what happen for union/intersection of lifetime. As you see, I've banged my head quite a lot on that one. I'm fairly confident that this is solvable, but definitively require a lot of effort to iron out all the details.- Aggregates: - It's turtles all the way down: members of scoped aggregates also have scoped type, with lifetime inherited from the parent aggregate. In other words, the lifetime of the aggregate is transitive to the lifetime of its members.OK, let's just say unscoped == scope with infinite lifetime. :)Get rid of the whole concept of unscopped, and you get rid of a whole class of redundant definition that needs to be done.- Passing parameters: since unscoped values are regarded to have infinite lifetime, it's OK to pass unscoped values into scoped function parameters: it's a narrowing of lifetime of the original value, which is allowed. (What's not allowed is expanding the lifetime of a scoped value.)[...] Maybe what we should do, is to have everyone post their current (probably incomplete) drafts of what scope should do, so that we have everything on the table and we can talk about what should be kept, what should be discarded, etc.. It may be, that the best design is not what any one of us has right now, but some combination of multiple current proposals. T -- Be in denial for long enough, and one day you'll deny yourself of things you wish you hadn't.Need some more iron. But I'm happy to see that some people came up with proposal that are close to what I had in mind.I'm sure there are plenty of holes in this proposal, so destroy away. ;-)
Jul 11 2014
On Friday, 11 July 2014 at 22:03:37 UTC, H. S. Teoh via Digitalmars-d wrote:Along these lines, I'm wondering if "turtles all the way down" is the wrong way of looking at it. Consider, for example, an n-level deep nesting of aggregates. If obj.nest1 is const, then obj.nest1.nest2.x must also be const, because otherwise we break the const system. So const is transitive downwards. But if obj.nest1 is a scoped reference type with lifetime L1, that doesn't necessarily mean obj.nest1.y only has lifetime L1. It may be a pointer that points to an infinite lifetime object, for example, so it's not a problem that the pointer goes out of scope before the object pointed to. OTOH, if obj.nest1 has scope L1, then obj itself cannot have a longer lifetime than L1, otherwise we may access obj.nest1 after its lifetime is over. So the lifetime of obj.nest1 must propagate *upwards* (or outwards).I'm not so sure about transitivity either, although I started with it. One reason that `const`, `immutable` and `shared` need to be transitive is that we can then use this fact to infer other properties from it, e.g. thread-safety. I don't really see such advantages for `scope`, but instead it would make handling `scope` in aggregates extremely complicated. And it wouldn't make much sense either IMO, because aggregates are usually defined somewhere else than where they are used, and often by a different author, too. Therefore, they come with their own ownership strategies, and it's simply not possible to force a different one onto them from the outside. For this reason, I now tend to intransitivity. There also something else that became clear to me: If an important use case is found that requires transitivity, nothing is really lost. We know all the types that are involved, and we can check whether all references contained in them are marked as `scope` using introspection, even without additional compiler support beyond a simple trait, just as we can today check for things like `hasUnsharedAliasing`! Therefore, we wouldn't even close the doors for further improvements if we decide for intransitivity.
Jul 12 2014
On Friday, 11 July 2014 at 22:03:37 UTC, H. S. Teoh via Digitalmars-d wrote:Maybe what we should do, is to have everyone post their current (probably incomplete) drafts of what scope should do, so that we have everything on the table and we can talk about what should be kept, what should be discarded, etc.. It may be, that the best design is not what any one of us has right now, but some combination of multiple current proposals.I've just done so for mine: http://wiki.dlang.org/User:Schuetzm/scope
Jul 13 2014
On Sun, Jul 13, 2014 at 02:58:27PM +0000, via Digitalmars-d wrote:On Friday, 11 July 2014 at 22:03:37 UTC, H. S. Teoh via Digitalmars-d wrote:Mine is here: http://wiki.dlang.org/User:Quickfur/DIP_scope T -- Turning your clock 15 minutes ahead won't cure lateness---you're just making time go faster!Maybe what we should do, is to have everyone post their current (probably incomplete) drafts of what scope should do, so that we have everything on the table and we can talk about what should be kept, what should be discarded, etc.. It may be, that the best design is not what any one of us has right now, but some combination of multiple current proposals.I've just done so for mine: http://wiki.dlang.org/User:Schuetzm/scope
Jul 14 2014
On 14/07/14 18:16, H. S. Teoh via Digitalmars-d wrote:Mine is here: http://wiki.dlang.org/User:Quickfur/DIP_scopeFrom the DIP: "The 'scope' keyword has been around for years, yet it is barely implemented and it's unclear just what it's supposed to mean" I don't know if it's worth clarify but "scope" currently has various features. 1. Allocate classes on the stack: "scope bar = new Bar()" 2. Forcing classes to be allocated on the stack: "scope class Bar {}" 3. The scope-statement: "scope(exit) file.close()" 4. Scope parameters. This is the part that is unclear what is means/is supposed to mean in the current language -- /Jacob Carlborg
Jul 14 2014
On 07/15/2014 08:42 AM, Jacob Carlborg wrote:On 14/07/14 18:16, H. S. Teoh via Digitalmars-d wrote:Isn't both 1 and 2 deprecated?Mine is here: http://wiki.dlang.org/User:Quickfur/DIP_scopeFrom the DIP: "The 'scope' keyword has been around for years, yet it is barely implemented and it's unclear just what it's supposed to mean" I don't know if it's worth clarify but "scope" currently has various features. 1. Allocate classes on the stack: "scope bar = new Bar()" 2. Forcing classes to be allocated on the stack: "scope class Bar {}" 3. The scope-statement: "scope(exit) file.close()" 4. Scope parameters. This is the part that is unclear what is means/is supposed to mean in the current language
Jul 14 2014
On 15/07/14 08:46, simendsjo wrote:Isn't both 1 and 2 deprecated?Depends on what you mean by "deprecated". People are keep saying that but it's not. Nothing, except for people saying that, indicates that. No deprecation message, no warning, nothing about it in the documentation. Even if/when that will be deprecated that it's not unclear what it does. -- /Jacob Carlborg
Jul 15 2014
On Tuesday, 15 July 2014 at 06:42:20 UTC, Jacob Carlborg wrote:1. Allocate classes on the stack: "scope bar = new Bar()" 4. Scope parameters. This is the part that is unclear what is means/is supposed to mean in the current languageThese are actually the same thing: if something is stack allocated, it must not allow the reference to escape to remain memory safe... and if the reference is not allowed to escape, stack allocating the object becomes an obvious automatic optimization. People keep calling them deprecated but they really aren't - the escape analysis to make it memory safe just isn't implemented.2. Forcing classes to be allocated on the stack: "scope class Bar {}"I think this is the same thing too, just on the class instead of the object, but I wouldn't really defend this feature, even if implemented correctly, since ALL classes really ought to be scope compatible if possible to let the user decide on their lifetime.
Jul 15 2014
On 15/07/14 14:47, Adam D. Ruppe wrote:These are actually the same thing: if something is stack allocated, it must not allow the reference to escape to remain memory safe... and if the reference is not allowed to escape, stack allocating the object becomes an obvious automatic optimization. People keep calling them deprecated but they really aren't - the escape analysis to make it memory safe just isn't implemented.Yes, I agree.I think this is the same thing too, just on the class instead of the object, but I wouldn't really defend this feature, even if implemented correctly, since ALL classes really ought to be scope compatible if possible to let the user decide on their lifetime.If a class is allocated on the stack, its destructor will be called (at least according to the spec). If you declare a class "scope" you know it will always be allocated on the stack and can take advantage of that. Even if all classes are "scope" compatible some might _only_ be compatible with "scope". -- /Jacob Carlborg
Jul 15 2014