Add __delete for migration from delete#2037
Conversation
|
Thanks for your pull request, @wilzbach! Bugzilla referencesYour PR doesn't reference any Bugzilla issue. If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog. |
|
|
||
| $(CONSOLE | ||
| sed "s/delete \(.*\);/__delete(\1);/" -i **/*.d | ||
| ) |
There was a problem hiding this comment.
I suggest adding something to the effect that this is intended to only be used as a last resort when destroy is not an option. At least that's my understanding: We want to migrate users to destroy.
There was a problem hiding this comment.
Potential verbage: "Users should prefer object.destroy to explicitly finalize objects, and only resort to __delete when object.destroy would not be a feasible option."
There was a problem hiding this comment.
The sed command could be a bit refined: s/\Wdelete[ \t]+\(.*\);/__delete(\1);/g
src/core/memory.d
Outdated
| This function has been added to provide an easy transition from `delete`. | ||
| It performs the same functionality as `delete`. | ||
|
|
||
| $(REF destroy, object) is `@safe` and should be used if possible. |
There was a problem hiding this comment.
Again, I think this should have some information explaining that users should first look to destroy and only to __delete when destroy is not an option.
src/foo.d
Outdated
| @@ -0,0 +1,151 @@ | |||
| import std.stdio; | |||
There was a problem hiding this comment.
What is this file (src/foo.d) doing in this commit?
|
Looks like you committed an extra file by mistake |
|
What advantages does this have over the (poorly named) destroy? |
|
Still, my understanding was that (years ago when delete was first put for deprecation) destroy was added as a migration path - by Andrei himself I believe. Obviously that function has failed spectacularly if this is needed. 🙄 |
A bit of history I could dug up, but it seems that |
| memory. | ||
| If the garbage collector was not used to allocate the memory for | ||
| the instance, undefined behavior will result. | ||
| ) |
There was a problem hiding this comment.
The implementation just sets the pointer to null. I don't think that initiates an immediate call to the GC to free the memory.
src/core/memory.d
Outdated
| a.test = "exists"; | ||
| auto b = &a; | ||
| auto c = &b; | ||
| delete c; |
There was a problem hiding this comment.
Shouldn't this be __delete?
src/core/memory.d
Outdated
| auto b = a; | ||
| __delete(b); | ||
| assert(b is null); | ||
| assert(a == [1, 2, 3]); |
There was a problem hiding this comment.
If I'm understanding correctly, you are accessing through a after b was __deleteed. According to the documentation comments, that's undefined behavior.
src/core/memory.d
Outdated
| } | ||
| else | ||
| { | ||
| static assert(0, "It is not possible to delete: " ~ T.stringof); |
There was a problem hiding this comment.
"It is not possible to delete`" ~ T.stringof ~ "`"
231 is titled "Rename clear to destroy". So it could always be my memory that is incorrect. :-) |
|
I am a bit surprised thought that Andrei wants this Personally, I think that anyone looking to do something like this should not be using GC-allocated memory, but it's not like we're completely against letting folks risk blowing their own feet off - we just don't want it to be easy to do that by default. |
changelog/core-memory-__delete.dd
Outdated
| `core.memory.__delete` has been added | ||
|
|
||
| $(REF __delete, core, memory) allows easy migration from the deprecated `delete`. | ||
| `__delete` behaves exactly like `delete` and will call the class dtor immediately |
There was a problem hiding this comment.
delete/__delete apply to structs and arrays as well, so please update the documentation
|
|
||
| $(CONSOLE | ||
| sed "s/delete \(.*\);/__delete(\1);/" -i **/*.d | ||
| ) |
There was a problem hiding this comment.
The sed command could be a bit refined: s/\Wdelete[ \t]+\(.*\);/__delete(\1);/g
src/core/memory.d
Outdated
| } | ||
| else static if (is(T == struct)) | ||
| { | ||
| _d_delstruct(cast(void**) &t, typeid(T)); |
There was a problem hiding this comment.
Wait, this is incorrect. __delete should be only applicable to pointers to struct. Let's not innovate here.
src/core/memory.d
Outdated
| } | ||
| else static if (is(T == U*, U)) | ||
| { | ||
| t = null; |
src/core/memory.d
Outdated
| } | ||
| else static if (is(T : E[], E)) | ||
| { | ||
| t = null; |
Thanks for the quip. The olden idea is to keep the safer |
915aeab to
444ab75
Compare
|
Alrighty I went over the implementation again and it now matches with DMD 1:1 -> https://github.com/dlang/dmd/blob/v2.078.0/src/dmd/e2ir.d#L3886 The only thing that I couldn't figure out is how to detect at compile-time whether |
changelog/core-memory-__delete.dd
Outdated
| `core.memory.__delete` has been added | ||
|
|
||
| $(REF __delete, core, memory) allows easy migration from the deprecated `delete`. | ||
| `__delete` behaves exactly like $(LINK2 $(ROOT_DIR)spec/expression.html#delete_expressions, `delete`): |
There was a problem hiding this comment.
One of the procedures we need to perform to deprecate and eventually remove delete is to remove all references to it in the spec (see dlang/dlang.org#1941). I suggest removing the link to the spec.
src/core/memory.d
Outdated
| there is a destructor for that class, the destructor | ||
| is called for that object instance. | ||
| ) | ||
| $(LI Otherwise, the garbage collector is called to immediately free the |
There was a problem hiding this comment.
This "otherwise" is ambiguous because it's applied to a conjunction. Does it refer to the fact that t is a class object reference, to the fact there is a destructor, or to the conjunction? (It applies to the second clause only). I'll make an edit to clarify.
There was a problem hiding this comment.
FYI: That was taken 1:1 from the spec ;-)
There was a problem hiding this comment.
@wilzbach @WalterBright and I are at POPL 2018 and one thing that became abundantly clear is we must overhaul the spec making it radically more precise. More on that later.
|
@wilzbach I just sent a patch to the doc - we should reuse |
src/core/memory.d
Outdated
| $(UL | ||
| $(LI | ||
| Calls `.object.destroy(*x)` (if `x` is a pointer) or `.object.destroy(x)` | ||
| (otherwise) to destroy the referred entity. |
There was a problem hiding this comment.
That's strictly speaking not true for class/interface objects and raw memory:
Exact behavior
- If
xis a class or interface object ->rt_finalize
Lines 163 to 167 in 098a08a
Of course, object.destroy does call rt_finalize:
Lines 2887 to 2896 in 098a08a
FTR rt_finalize is deprecated, but rt_finalize2 does call a few interesting things:
Lines 1382 to 1419 in 098a08a
- If
xis a reference to a struct ->Typeinfo_Struct.destroy:
Lines 177 to 183 in 098a08a
Here it's all about calling the dtor:
Lines 1194 to 1199 in 098a08a
- If
xis a reference to an arrays -> for all structs within:Typeinfo_Struct.destroy:
Line 1169 in 098a08a
Lines 1347 to 1356 in 098a08a
- Otherwise only
GC.freeis called.
src/core/memory.d
Outdated
| Frees the memory allocated for `x`. If `x` is a reference to a class | ||
| or interface, the memory allocated for the underlying instance is freed. If `x` is | ||
| a pointer, the memory allocated for the pointed-to object is freed. If `x` is a | ||
| built-in array or associative array, the memory allocated for the array is freed. |
There was a problem hiding this comment.
Associative arrays aren't allowed: https://run.dlang.io/is/CtfP8f
src/core/memory.d
Outdated
| ) | ||
|
|
||
| Params: | ||
| t = aggregate object that should be destroyed |
There was a problem hiding this comment.
Reminder to myself: I will rename this to x then.
|
From my memory:
IMO, the |
Nice (I just removed your trailing white-space in case you were wondering why I force-pushed your commit). |
|
@wilzbach yah I wonder if we could submit a github issue - would be great if their editor supported trailing space elimination |
|
@wilzbach agreed about the discrepancies - in this case we must work backwards: |
https://github.com/isaacs/github
What do you suggest? Do you want to change I would simply change the wording to sth. like:
|
|
Yes, |
src/core/memory.d
Outdated
| $(LI | ||
| Calls `.object.destroy(x)` (if `x` is a class or interface object) or | ||
| `typeid(*x).destroy(x)` (if `x` is pointer to a struct) to destroy the referred entity. | ||
| Arrays of structs call `typeid(*x).destroy(x)` for each element. |
There was a problem hiding this comment.
This could also state: Arrays of structs call typeid(x).destroy(&x) for each element (that's what the implementation does below).
There was a problem hiding this comment.
I think it should be .object.destroy(e) for each element e in the array.
| assert(a is null); | ||
| } | ||
|
|
||
| // __delete returns with no effect if x is null |
There was a problem hiding this comment.
Is there a simple way to add a test this for "no effect"?
src/core/memory.d
Outdated
| $(UL | ||
| $(LI | ||
| Calls `.object.destroy(x)` (if `x` is a class or interface object) or | ||
| `typeid(*x).destroy(x)` (if `x` is pointer to a struct) to destroy the referred entity. |
There was a problem hiding this comment.
Here it should just be .object.destroy(*x), no? It's faster and better documented.
There was a problem hiding this comment.
The former is an indirect call and the second is a direct call. Truth be told, destroy does more work because it reinitializes the object. In the case of __delete we don't care to reinitialize the object because we just wrote in the documentation that access after __delete is undefined behavior.
So all in all the best way to go is to write in the documentation that we call destroy and actually call _destructRecurse. That covers all defined cases and is fast.
There was a problem hiding this comment.
_destructRecurse is private and in object - so we can't even do package(core).
src/core/memory.d
Outdated
| $(LI | ||
| Calls `.object.destroy(x)` (if `x` is a class or interface object) or | ||
| `typeid(*x).destroy(x)` (if `x` is pointer to a struct) to destroy the referred entity. | ||
| Arrays of structs call `typeid(*x).destroy(x)` for each element. |
There was a problem hiding this comment.
I think it should be .object.destroy(e) for each element e in the array.
src/core/memory.d
Outdated
| // See also: https://github.com/dlang/dmd/blob/v2.078.0/src/dmd/e2ir.d#L3886 | ||
| static if (is(T == interface)) | ||
| { | ||
| (cast(Object) x).destroy; |
There was a problem hiding this comment.
I think this should do what the spec says it does: .object.destroy(cast(Object) x); This is a pedantic point because Object does not define a method destroy.
src/core/memory.d
Outdated
| } | ||
| else static if (is(T == class)) | ||
| { | ||
| (cast(Object) x).destroy; |
There was a problem hiding this comment.
Same here, .object.destroy(cast(Object) x); would clarify lookup
src/core/memory.d
Outdated
| else static if (is(T == U*, U)) | ||
| { | ||
| static if (is(U == struct)) | ||
| typeid(U).destroy(x); |
There was a problem hiding this comment.
As mentioned, I think this is less efficient - it entails an indirect call instead of simply calling the destructor (if defined) or doing nothing (if not). I suggest: .object.destroy(*x)
src/core/memory.d
Outdated
| static if (is(E == struct)) | ||
| { | ||
| foreach (ref e; x) | ||
| typeid(E).destroy(&e); |
|
|
||
| static if (is(T == interface) || is(T == class) || is(T == U2*, U2) || is(T : E2[], E2)) | ||
| { | ||
| GC.free(&x); |
There was a problem hiding this comment.
I'm not sure about this in the case of classes and interfaces. For those shouldn't the call be GC.free(cast(void*) x)?
There was a problem hiding this comment.
Added tests for GC.free
src/core/memory.d
Outdated
| $(UL | ||
| $(LI | ||
| Calls `.object.destroy(x)` (if `x` is a class or interface object) or | ||
| `(*x).__xdtor()` (if `x` is pointer to a struct and a custom destructor exists) to destroy the referred entity. |
There was a problem hiding this comment.
That's exactly what we do, but I wasn't whether there's a more elegant way to put it - __xdtor is a bit ugly, but it's still better than ".object.destroy(*x), but doesn't initialize the struct to T.init"
There was a problem hiding this comment.
People cannot be expected to know what __xdtor does, but they do know what a destructor is. So the text should be:
"Calls the destructor ~this() for the object referred to by x (if x is a class or interface reference) or for the object pointed to by x (if x is a pointer to a struct). If no destructor is defined, this step has no effect."
|
So are we ready to 🚀 here? |
src/core/memory.d
Outdated
| pragma(mangle, "free") void fakePureFree(void* ptr); | ||
| } | ||
|
|
||
| extern(C) private @system nothrow |
src/core/memory.d
Outdated
| The `delete` keyword allowed to free GC-allocated memory. | ||
| As this is inherently not `@safe`, it has been deprecated. | ||
| This function has been added to provide an easy transition from `delete`. | ||
| It performs the same functionality as `delete`. |
There was a problem hiding this comment.
Instead of assuming people know delete and defining __delete in terms of it, describe __delete as "destroy then deallocate" and at the very end "see also the deprecated keyword" etc.
src/core/memory.d
Outdated
| $(UL | ||
| $(LI | ||
| Calls `.object.destroy(x)` (if `x` is a class or interface object) or | ||
| `(*x).__xdtor()` (if `x` is pointer to a struct and a custom destructor exists) to destroy the referred entity. |
There was a problem hiding this comment.
People cannot be expected to know what __xdtor does, but they do know what a destructor is. So the text should be:
"Calls the destructor ~this() for the object referred to by x (if x is a class or interface reference) or for the object pointed to by x (if x is a pointer to a struct). If no destructor is defined, this step has no effect."
| Calls `.object.destroy(x)` (if `x` is a class or interface object) or | ||
| `(*x).__xdtor()` (if `x` is pointer to a struct and a custom destructor exists) to destroy the referred entity. | ||
| Arrays of structs with a custom destructor call `x.__xdtor()` for each element in the array. | ||
| ) |
There was a problem hiding this comment.
"Arrays of structs call the destructor, if defined, for each element in the array."
src/core/memory.d
Outdated
| void __delete(T)(ref T x) @system | ||
| { | ||
| static void _destructRecurse(S)(ref S s) | ||
| if (is(S == struct)) |
| { | ||
| static if (__traits(hasMember, S, "__xdtor") && | ||
| // Bugzilla 14746: Check that it's the exact member of S. | ||
| __traits(isSame, S, __traits(parent, s.__xdtor))) |
There was a problem hiding this comment.
I see the bugzilla is fixed, why do we need this?
There was a problem hiding this comment.
Because fixing 14746, added this to _destructRecurse: 1a2290b
a675d5c to
57cbf25
Compare
Sorry. Pong. Addressed the final nits & squashed everything into one commit. |
See also:
I didn't add any tests for Class deallocators
as they have been deprecated since D2 and we call the existing functions.
CC @JinShil