On Borrow Checking

Dennis dkorpel at gmail.com
Mon May 12 13:25:50 UTC 2025


On Saturday, 10 May 2025 at 12:10:48 UTC, Manu wrote:
> Okay, so then why should `scope` need `@safe`?

The escape checker can only maintain its invariants in `@safe` 
code, because in `@system` code, anything goes. scope pointers 
can be laundered by casting to size_t and back for example. Now 
you could argue that the same holds for `const` and `immutable`, 
but we still do the checks in `@system` code for linting 
purposes, requiring an explicit `cast()` to remove `const`. I'm 
not against scope checking in `@system` code per se, but there 
are some differences that make it more complicated. There is 
currently no `cast(notscope)` for example.

> It's an additional attribute added in its own right; there's no 
> apparent value to requiring a SECOND attribute's presence in 
> order to make the first attribute take effect. `scope` should 
> work when you write `scope`. If you don't want scope checking, 
> don't write `scope`...?

While there's in theory a single meaning of `scope`, in practice, 
the attribute is used for different things:

1. Document the lifetime of a variable
2. Let the compiler enforce that lifetime
3. Turn GC allocations of classes / arrays into stack allocations
4. Run class destructors deterministically

While those usually align, different people put a different 
aspect first. For example, some consider `scope` to be basically 
C#'s stackalloc and believe the programmer is straight up asking 
to overflow the stack here:

```D
void main()
{
     scope int[] arr = new int[32_000_000];
}
```

https://github.com/dlang/dmd/issues/20770#issuecomment-2615115810

Others may just care about running a class destructor. Now what 
happens if we enable scope checking everywhere?

```D
void main() @system
{
     scope Object o = new Object();
     const h = o.toHash();
     // Error: scope variable `o` calling non-scope member 
function `Object.toHash()`
}
```

Oops, that method is not marked `scope`. class methods rarely 
escape their `this` pointer, but since it is possible to override 
methods, scope can't be inferred, requiring annoying and ugly 
scope annotations everywhere.

A similar issue can happen with ImportC:

```D
import glfw;

void copy(scope const(char)[] str) @trusted
{
     assert(str.length > 0 && str[$ - 1] == '\0');
     glfwSetClipboardString(window, str.ptr);
}
```

I've read the documentation of glfw which says 
`glfwSetClipboardString` copies the input string, but I can't 
annotate the parameter in glfw.h with `scope` because it doesn't 
exist in C.

Finally, mechanical scope checking has false positives:
```D
void f(ref scope S ctx)
{
     const old = ctx;
     g(ctx);
     ctx = old; // Error: scope variable `old` assigned to `ref` 
variable `ctx` with longer lifetime

}
```
Some of these are bugs / limitations which can be improved, but 
in general it's impossible to do perfect scope checking (Rice's 
theorem). Even your example of assigning a scope pointer to a 
global variable can be valid if you manually restrict read access 
to that global to the current scope using `@system` variables and 
the right `@trusted` code.

Now like I said, I'm not against the idea per se. I have recently 
debugged a use-after-free bug from returning a scope array in 
code that I didn't mark `@safe` yet, which would have been caught 
immediately otherwise. But the considerations are:

- Code breakage
- Different expectations of `scope` from different people
- Nuisance with scope classes
- Passing scope pointers to C functions
- False positive errors
- "Trust me, this parameter `scope`"




More information about the Digitalmars-d mailing list