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