Thread overview
preview in: it seems that return type auto solves scope variable assignment to this
Feb 09, 2023
Dmytro Katyukha
Feb 09, 2023
Dennis
Feb 09, 2023
Dmytro Katyukha
Feb 10, 2023
tsbockman
February 09, 2023

Hi,

Not sure it is bug, or feature, or what is going on in examples below (with -preview=in enabled).

For example, we have following code snippet:

import std.stdio;
import std.path;

struct Path {
    private string _path;

    @safe pure nothrow this(in string path) {
        _path = path.dup;
    }

    @safe pure nothrow this(in string[] segments...) {
        this(std.path.buildNormalizedPath(segments));
    }

    /// Check if current path is inside other path
    @safe bool isInside(in Path other) const {
        import std.algorithm: startsWith;
        return this.toAbsolute.segments.startsWith(
            other.toAbsolute.segments);
    }

    @safe Path toAbsolute() const {
        return Path(
            std.path.buildNormalizedPath(
                std.path.absolutePath(_path.dup.expandTilde)));
    }

    @safe pure auto segments() const {
        return std.path.pathSplitter(_path);
    }

}

unittest {
    assert(Path("my", "dir", "42").isInside(Path("my", "dir")) == true);
    assert(Path("my", "dir", "42").isInside(Path("oth", "dir")) == false);
}

void main()
{
    auto p = Path(".");
    auto h = Path("~");
    auto r = p.isInside(h);
}

Without -preview=in option, this code sample works just fine.
But when i enable -preview=in (or just manualy replace in with scope const in isInside method) it fails with following error:

source/app.d(19,13): Error: scope variable `other` assigned to non-scope parameter `this` calling `toAbsolute`

So, the first question is: where in this code assignment to this happens?

But, what is more strange, is that if i change return type for method toAbsolute to auto, error disappears:

-    @safe Path toAbsolute() const {
+    @safe auto toAbsolute() const {

So, the second question is: what happens in this case?

PS: What is the status of preview in? Do it have sense to take it into account?

February 09, 2023

On Thursday, 9 February 2023 at 17:42:00 UTC, Dmytro Katyukha wrote:

>

So, the first question is: where in this code assignment to this happens?

When calling a member function like other.toAbsolute, you're assigning other to the implicit this parameter, as if you are calling toAbsolute(this: other).

>

So, the second question is: what happens in this case?

Functions with auto return get attributes inferred, just like template functions. The compiler infers scope based on the implementation of toAbsolute.

February 09, 2023

On Thursday, 9 February 2023 at 17:59:39 UTC, Dennis wrote:

>

On Thursday, 9 February 2023 at 17:42:00 UTC, Dmytro Katyukha wrote:

>

So, the first question is: where in this code assignment to this happens?

When calling a member function like other.toAbsolute, you're assigning other to the implicit this parameter, as if you are calling toAbsolute(this: other).

>

So, the second question is: what happens in this case?

Functions with auto return get attributes inferred, just like template functions. The compiler infers scope based on the implementation of toAbsolute.

Hi, thank you. Got it)

So, the next questions:

  • Do it have sense to explicitly annotate all (most) member methods of a struct with scope attribute? Or it is better to use auto everywhere?.
  • What drawbacks of annotating all (most) parameters as scope? Is it correct approach?
  • What about in parameter attribute? Why it is not used wide in Phobos?

How to correctly call method from standard lib (for example expandTilde) that does not annotated param with scope attribute?

For example:

@safe unittest {
    scope string p1 = "~/projects/d";
    scope string p2 = expandTilde(p1);
    assert(p1 != p2);
}

In this case, following error raised:

source/app.d(41,35): Error: scope variable `p1` assigned to non-scope parameter `inputPath` calling `expandTilde`

Is it correct, to use .dup method?

 @safe unittest {
     scope string p1 = "~/projects/d";
-    scope string p2 = expandTilde(p1);
+    scope string p2 = expandTilde(p1.dup);
     assert(p1 != p2);
 }
February 10, 2023

On Thursday, 9 February 2023 at 21:28:54 UTC, Dmytro Katyukha wrote:

>

So, the next questions:

  • Do it have sense to explicitly annotate all (most) member methods of a struct with scope attribute?
    ...
  • What drawbacks of annotating all (most) parameters as scope?

scope restricts what a function's implementation can do, while giving more freedom to the caller. (Mostly, this is the freedom to allocate less things with the GC.)

This is generally a good thing, provided that you understand when scope and return scope apply well enough not to be slowed down or confused by them too much.

Of course, sometimes you will actually have a good reason to escape a reference to a global or whatever, and scope doesn't do anything for types without indirections, so obviously you shouldn't put scope everwhere.

>

Or it is better to use auto everywhere?.

Whether it is better to explicitly write out attributes and types, or have the compiler infer them via auto and/or templatization, depends on whether you want the compiler to give you an error message if you change the implementation in a way that alters the API, and how you value explicit documentation of the API versus keeping your code concise.

I recommend being as explicit as practical for public APIs, and for any aspect of any API that is fundamental to its purpose. (For example, a function intended to be used in CTFE should be explicitly pure.)

Just be careful not to over-constrain template APIs without a good reason.

>
  • What about in parameter attribute? Why it is not used wide in Phobos?

Most of Phobos was written before the semantics of in or scope were finalized. That's why there is -preview=in and -dip1000, because them actually working is a somewhat new, work-in-progress feature.

>

How to correctly call method from standard lib (for example expandTilde) that does not annotated param with scope attribute?

If there is no obvious valid reason for the function to be escaping one of its parameters, it's likely a bug in Phobos - a bit of code that hasn't been fully updated yet for -dip1000. This is especially likely to be the case if the parameter in question is not immutable.

>

For example:

@safe unittest {
    scope string p1 = "~/projects/d";
    scope string p2 = expandTilde(p1);
    assert(p1 != p2);
}

In this case, following error raised:

source/app.d(41,35): Error: scope variable `p1` assigned to non-scope parameter `inputPath` calling `expandTilde`

Is it correct, to use .dup method?

 @safe unittest {
     scope string p1 = "~/projects/d";
-    scope string p2 = expandTilde(p1);
+    scope string p2 = expandTilde(p1.dup);
     assert(p1 != p2);
 }

Either use .dup, .idup, or don't make p1 scope in the first place.

The last option is preferred if you know you would otherwise need to .dup the majority of the time. Also, the compiler is generally pretty good about inferring scope where needed for local stack variables, so scope should mostly only appear explicitly when receiving scope parameters, not when sending them.