https://issues.dlang.org/show_bug.cgi?id=19183
--- Comment #12 from ag0aep6g <ag0ae...@gmail.com> --- (In reply to Atila Neves from comment #11) > > You're still just copying an `int*` around, which isn't unsafe. > > Of course it is, that's basically the whole point of Rust and DIP1000. You're right. Of course DIP 1000 is about restricting how pointers can be copied around. I was completely wrong here. Didn't really think in DIP 1000 terms. Sorry for the noise. [...] > From DIP1000: > > "For all global and static variables, lifetime is infinite." > "For values allocated on the garbage collected heap, lifetime is infinite > whilst reachability is dependent on the references in the program bound to > those values." > > Algebra of lifetimes lists "*e", "new", "e[i]", "ArrayLiteral" and > "ArrayLiteral[constant]" as the only expressions with infinite lifetime. > > And again: > > "A variable is inferred to be scope if it is initialized with a value that > has a non-∞ lifetime." > > > Therefore, since `auto s = MyStruct(10)` doesn't match any of the above > conditions for an infinite lifetime, `auto` or `scope` should be the same > thing. DIP 1000 on `null`: "lifetime(null) is infinite". `null` is an instance of a global, I guess. On function calls, it says that the lifetime of the result is defined in the "section dedicated to discussing functions". Unfortunately, that section doesn't mention "lifetime" even once, and I find it rather hard to figure out what the lifetime of a function call is supposed to be when the function body isn't available for analysis. So if we leave the pointer as `null`, it has infinite lifetime. And it's okay to copy it around. This is what happens in all the posted snippets that don't have a `malloc` call. For `malloc(...)` it's not clear to me what the lifetime is supposed to be. Apparently, DMD goes with infinite. The consequence is that the `free` call always comes as a surprise to the compiler. I.e., calling `free` always breaks the @trusted promise, and @safe becomes unreliable. The best we can do with this is using an @trusted `free` anyway and containing the fallout. What if `malloc(...)` had a zero lifetime instead? Then any access to a field initialized from it would have to be @trusted, because its lifetime would always be considered over. Since @safe code wouldn't be able to even look at the field, @safe might stay reliable. I'm not sure if this could work out. I suppose the goal is to somehow get access to the field with proper lifetime (i.e. tied to the struct instance, if the destructor `free`s). With the current implementation we might do it like this: ---- --- main.d import the_ugly; void* global; void main() @safe { auto s = S(1); int* local = s.p; static assert(!__traits(compiles, global = s.p)); } --- the_ugly.d struct S { private int* p_; int* p() return @safe { return p_; } this(int n) @trusted { import core.stdc.stdlib: malloc; p_ = cast(int*) malloc(int.sizeof); } ~this() @trusted { import core.stdc.stdlib: free; free(p_); } } ---- The problem is of course that the whole module the_ugly must be verified manually. @safe and attribute inference cannot be relied upon. That's brittle. Could a method like `p` be written if DMD chose a zero lifetime for `malloc(...)` instead of an infinite one? If so, that might be nice. `p` would probably have to be @trusted, but @safe and attribute inference could be relied upon for other methods and `p_` wouldn't even have to be private. > > I'm not sure if I understand that correctly, but this compiles just fine: > > I'm arguing it shouldn't. You said it "doesn't compile". Or more precisely: You described some code that "doesn't compile" and I couldn't reproduce it from the description. If the code you thought of is different from the snippet I pieced together, maybe show it if it's interesting. --