Jump to page: 1 2
Thread overview
'scope' confusion
Apr 08, 2022
Andy
Apr 08, 2022
Dukc
Apr 15, 2022
Andy
Apr 15, 2022
Paul Backus
Apr 15, 2022
Dom DiSc
Apr 15, 2022
Loara
Apr 15, 2022
Walter Bright
Apr 15, 2022
Salih Dincer
May 01, 2022
Andy
May 01, 2022
Paul Backus
May 02, 2022
Dennis
April 08, 2022

In the following code, I can pass a non-scope argument to a function expecting a scope parameter, but I can't pass a scope argument. That seems backwards?

@safe @nogc pure nothrow:

void main() {
	string[2] notScope = ["one", "two"];
	f(notScope);

	scope string[2] yesScope = ["one", "two"];
	f(yesScope);
}

void f(scope string[] expectingScope) {}

Output (of dmd a.d -preview=dip1000):

a.d(8): Error: cannot take address of `scope` local `yesScope` in `@safe` function `main`
a.d(8): Error: cannot cast expression `yesScope` of type `string[2]` to `string[]`

The error only happens in dmd 2.099.0 -- it didn't happen in 2.098.1 .


There is another even simpler case that doesn't work in either version:

@safe @nogc pure nothrow:

void main() {
	scope string[2] xs = ["one", "two"];
	foreach (scope immutable string x; xs) {}
}

Output (of dmd a.d -preview=dip1000):

a.d(5): Error: cannot take address of `scope` local `xs` in `@safe` function `main`
April 08, 2022

On Friday, 8 April 2022 at 05:52:09 UTC, Andy wrote:

>

In the following code, I can pass a non-scope argument to a function expecting a scope parameter, but I can't pass a scope argument. That seems backwards?

I was already writing "these both should pass", but then I noticed what's wrong.

The scope at f means you cannot escape the outer array. But on yesScope, it means that you cannot escape the strings! In other words, this fails for the same reason that this would fail:

@safe void main()
{ scope immutable(char)[] a = "one";
  scope immutable(char)[] b = "two";
  auto array = [a, b];
}

It fails, because D does not have a concept of an array holding scope variables (or a pointer pointing to a scope variable). Only the outernmost array or pointer can be scope. That's with dynamic arrays.

However, with static arrays and structs, scope means that each member of them is scope. Nothing else would make sense, because these contain their data in the variable itself. Assign a static array or a struct to new one, and it's contents becomes and independent copies of the original. Data escaping them is no more issue than 5 escaping an int.

April 15, 2022

On Friday, 8 April 2022 at 07:02:57 UTC, Dukc wrote:

>

On Friday, 8 April 2022 at 05:52:09 UTC, Andy wrote:

>

In the following code, I can pass a non-scope argument to a function expecting a scope parameter, but I can't pass a scope argument. That seems backwards?

I was already writing "these both should pass", but then I noticed what's wrong.

The scope at f means you cannot escape the outer array. But on yesScope, it means that you cannot escape the strings! In other words, this fails for the same reason that this would fail:

@safe void main()
{ scope immutable(char)[] a = "one";
  scope immutable(char)[] b = "two";
  auto array = [a, b];
}

It fails, because D does not have a concept of an array holding scope variables (or a pointer pointing to a scope variable). Only the outernmost array or pointer can be scope. That's with dynamic arrays.

However, with static arrays and structs, scope means that each member of them is scope. Nothing else would make sense, because these contain their data in the variable itself. Assign a static array or a struct to new one, and it's contents becomes and independent copies of the original. Data escaping them is no more issue than 5 escaping an int.

Is there any way to do this safely?

@safe @nogc pure nothrow:

void f() {
	scope immutable Node a = Node(1, null);
	g(a);
}

void g(scope immutable Node a) {
	scope immutable Node b = Node(2, &a);
}

struct Node {
	immutable int head;
	immutable Node* tail;
}

I would think that getting &a in that context ought to be safe because it's only used in a scope variable, but apparently not.
I'm using immnutable here because I know a.tail = b would be wrong; they're both scope but a is a parameter and thus outlives b.

To put it more simply, why doesn't this work?

@safe @nogc pure nothrow:

void f() {
	int x = 0;
	scope int* y = &x;
}
April 15, 2022

On Friday, 15 April 2022 at 05:42:42 UTC, Andy wrote:

>

Is there any way to do this safely?

@safe @nogc pure nothrow:

void f() {
	scope immutable Node a = Node(1, null);
	g(a);
}

void g(scope immutable Node a) {
	scope immutable Node b = Node(2, &a);
}

struct Node {
	immutable int head;
	immutable Node* tail;
}

No, because scope isn't transitive. If the above example were allowed, then b.tail.tail would not be scope.

Here's a simplified version, which makes the levels of indirection more obvious:

@safe @nogc pure nothrow:

void f() {
	scope immutable int* a = null;
	g(a);
}

void g(scope immutable int* a) {
	scope immutable int** b = &a; // error
}
>

To put it more simply, why doesn't this work?

@safe @nogc pure nothrow:

void f() {
	int x = 0;
	scope int* y = &x;
}

That example does work. It's only when you get to two levels of indirection that it fails; for example:

@safe @nogc pure nothrow:

void f() {
	int x = 0;
	scope int* y = &x;
	scope int** z = &y; // error
}
April 15, 2022

On Friday, 15 April 2022 at 06:12:06 UTC, Paul Backus wrote:

>

No, because scope isn't transitive. [...]
That example does work. It's only when you get to two levels of indirection that it fails; for example:

@safe @nogc pure nothrow:

void f() {
	int x = 0;
	scope int* y = &x;
	scope int** z = &y; // error
}
If it's not transitive, perhaps we need something to make it explicit:
```d
void f() {
   int x = 0;
   scope int* y = &x;
   scope int* scope* z = &y; // like const* in C++
}
```
April 15, 2022

On Friday, 15 April 2022 at 10:31:31 UTC, Dom DiSc wrote:

>

On Friday, 15 April 2022 at 06:12:06 UTC, Paul Backus wrote:

>

No, because scope isn't transitive. [...]
That example does work. It's only when you get to two levels of indirection that it fails; for example:

@safe @nogc pure nothrow:

void f() {
	int x = 0;
	scope int* y = &x;
	scope int** z = &y; // error
}
If it's not transitive, perhaps we need something to make it explicit:
```d
void f() {
   int x = 0;
   scope int* y = &x;
   scope int* scope* z = &y; // like const* in C++
}
```

Actually the documentation is a bit confusing about scope storage class and which operations are admitted for scoped variables/function parameters. I think the role of scope inside D is not well defined at this moment (and the original DIP1000 was written even worse) so until this feature will be fixed we should avoid use it in released cose (and use const instead of in)

April 15, 2022
The trouble here is the scope array is a static array. This means the scope applies to the elements of the static array.

But when passing it to the function, the static array is converted to a dynamic array. A dynamic array is a pointer to the array's elements.

The scope protections are not transitive, so a scope dynamic array does not protect its elements as scope, hence the errors you're seeing.
April 15, 2022

On Friday, 15 April 2022 at 20:43:19 UTC, Walter Bright wrote:

>

The trouble here is the scope array is a static array. This means the scope applies to the elements of the static array.

But when passing it to the function, the static array is converted to a dynamic array. A dynamic array is a pointer to the array's elements.

The scope protections are not transitive, so a scope dynamic array does not protect its elements as scope, hence the errors you're seeing.

A very good explanation. Thank you very much for my own name.

SDB@79

May 01, 2022

I have another puzzle related to scoping. In this case it's about defining data structures that will be compatible with scope.

@safe @nogc pure nothrow:

extern(C) void main() {}

struct ArrayWrapper(T) {
	private T[] inner;

	ref inout(T) opIndex(immutable size_t index) inout {
		return inner[index];
	}
}

struct SomeValue {
	int* a;
}

int* f0(int[] xs) {
	return &xs[0];
}

SomeValue* f1(SomeValue[] xs) {
	return &xs[0];
}

int* g0(ArrayWrapper!int xs) {
	return &xs[0];
}

SomeValue* g1(ArrayWrapper!SomeValue xs) {
	return &xs[0];
}

Compiling this code results in an error in g1 only:

a.d(30): Error: cannot take address of `ref return` of `xs.opIndex()` in `@safe` function `g1`

Writing return &xs.inner[0]; would work .. which is exactly what opIndex is supposed to do. The question is how to get the compiler to understand this. How can I write ArrayWrapper so that it behaves the same way an array does?

May 01, 2022

On Sunday, 1 May 2022 at 04:04:04 UTC, Andy wrote:

>

Compiling this code results in an error in g1 only:

a.d(30): Error: cannot take address of `ref return` of `xs.opIndex()` in `@safe` function `g1`

Writing return &xs.inner[0]; would work .. which is exactly what opIndex is supposed to do. The question is how to get the compiler to understand this. How can I write ArrayWrapper so that it behaves the same way an array does?

Simplified example:

int** p;

ref int* get() @safe
{
    return *p;
}

int** g1() @safe
{
    return &get(); // error
}

Looks like the compiler has a blanket rule against taking the address of a ref return value if the value's type contains indirections.

« First   ‹ Prev
1 2