RFC: scope and borrowing

via Digitalmars-d digitalmars-d at puremagic.com
Thu Sep 11 13:45:08 PDT 2014


On Thursday, 11 September 2014 at 16:32:54 UTC, Ivan Timokhin 
wrote:
> I am in no way a language guru, but here are a few things that 
> bother me in your proposal. Thought I'd share.

Neither am I :-)

>
> 1. AFAIK, all current D type modifiers can be safely removed 
> from the topmost level (i.e. it is OK to assign 
> immutable(int[]) to immutable(int)[]), because they currently 
> apply to particular variable, so there's no good reason to 
> impose same restrictions on its copy. Situation seems different 
> with scope: it is absolutely not safe to cast away and it 
> applies to a *value*, not a variable holding it.

The types in your example are implicitly convertable, indeed no 
explicit cast is necessary. This is because when you copy a const 
value, the result doesn't need to be const. But with scope, it 
makes sense (and is of course necessary) to keep the ownership. I 
don't see that as an inconsistency, but as a consequence of the 
different things const and scope imply: mutability vs. ownership.

>
> This is not only inconsistent, but may also cause trouble with 
> interaction with existing features. For example, what should be 
> std.traits.Unqual!(scope(int*)) ?

Good question. I would say it needs to keep scope, as it was 
clearly designed with mutability in mind (although it also 
removes shared, which is however related to mutability in a way). 
Ownership is an orthogonal concept to mutability.

>
> 2. Consider findSubstring from your examples. What should be 
> typeof(findSubstring("", ""))? Is the following code legal?
>
> 	scope(string) a = ..., b = ...;
> 	...
> 	typeof(findSubstring("", "")) c = findSubstring(a, b);
>

It's not legal. String literals live forever, so `c` has an owner 
that lives longer than `a` and `b`.

An alternative interpretation would be that the literals are 
temporary expressions; then it would have a very short lifetime, 
thus the assignment would be accepted. But I guess there needs to 
be a rule that says that the specified owners must not live 
shorter than the variable itself.

> This is a bit troublesome, because this is how things like 
> std.range.ElementType work currently, so they may break. For 
> example,
> what would be ElementType!ByLineImpl (from the 
> "scope(const...)" section)?

I see... it can _not_ be:

     scope!(const ByLineImpl!(char, "\n").init)(ByLineImpl!(char, 
"\n"))

because the init value is copied and thus becomes a temporary. 
This is ugly. It would however work if ElementType would take an 
instance instead of a type.

>
> This troubles me the most, because currently return type of a 
> function may depend only on types of its arguments, and there 
> is a lot of templated code written in that assumption.

I'm sorry, I don't understand what you mean here. This is clearly 
not true, neither for normal functions, nor for templates.

> With the current proposal it ALL could break. Maybe there's no 
> way around it if we want a solid lifetime management system, 
> but I think this is definitely a problem to be aware of.

The answer may be that scope needs to be something independent 
from the type, indeed more like a storage class, rather than what 
I suggested a type modifier. This would also solve the problem 
about `std.traits.Unqual`, no?

I'd have to think this through, but I believe this is indeed the 
way to go. It would make several other things cleaner. On the 
other hand, it would then be impossible to have scoped member 
fields, because storage classes aren't usable there, AFAIK. This 
would need to be supported first.

>
> 3. I believe it was mentioned before, but shouldn't scope 
> propagate *outwards*? This would not only make perfect sense, 
> since the aggregate obviously "holds the reference" just as 
> well as its member does, it would also make various 
> range-wrappers and alike automatically scope-aware, in that the 
> wrapper would automatically become scoped if the wrapped range 
> is scoped.

You mean that any aggregate that contains a member with owner X 
automatically gets X as its owner itself?

I don't think so, because assigning a struct is semantically 
equivalent to assigning its members individually one after the 
others (by default) or whatever opAssign() is implemented to do. 
This means that an assignment that violates the rules would fail 
anyway, because the ownership is codified as part of the member's 
type. Instances of wrapper types would also need to be declared 
as scope with the appropriate owner, because otherwise they could 
not contain the scoped variables.

But I'm not sure how this is supposed to work with the storage 
class version of scope.


More information about the Digitalmars-d mailing list