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:
- Document the lifetime of a variable
- Let the compiler enforce that lifetime
- Turn GC allocations of classes / arrays into stack allocations
- 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:
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?
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:
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:
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
"