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.

Reply via email to