www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Stop writeln from calling object destructor

reply data pulverizer <data.pulverizer gmail.com> writes:
I've noticed that `writeln` calls the destructor of a struct 
multiple times and would like to know how to stop this from 
happening. It has become a very serious problem when working with 
objects that have memory management external to D.

Here is a repeatable example, where the destructor appears to 
have been called 4 times with one call of `writeln` before the 
object actually goes out of scope:


Code:
```
import std.stdio: writeln;

struct MyObject
{
     int id;
     this(int id)  nogc
     {
         this.id = id;
     }
     ~this()
     {
         writeln("Object destructor ...");
     }
}



void main()
{
     auto obj = MyObject(42);
     writeln("MyObject: ", obj);
     writeln("Goodbye:\n");
}

```

Output:
```
$ rdmd gc.d
MyObject: MyObject(42)Object destructor ...
Object destructor ...

Object destructor ...
Object destructor ...
Goodbye:

Object destructor ...
```

Thank you
Oct 02 2022
next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Sunday, 2 October 2022 at 16:21:47 UTC, data pulverizer wrote:
 I've noticed that `writeln` calls the destructor of a struct 
 multiple times and would like to know how to stop this from 
 happening.
It's because `writeln` is copying the object, and each of the copies is being destroyed. If you add a copy constructor to your example, you can see it happening: ```d import std.stdio: writeln; struct MyObject { int id; this(int id) nogc { this.id = id; } this(inout ref MyObject) inout { writeln("Object copy constructor..."); } ~this() { writeln("Object destructor ..."); } } void main() { auto obj = MyObject(42); writeln(obj); writeln("Goodbye:\n"); } ``` Output: ```d Object copy constructor... Object copy constructor... Object copy constructor... Object copy constructor... MyObject(0)Object destructor ... Object destructor ... Object destructor ... Object destructor ... Goodbye: Object destructor ... ```
Oct 02 2022
parent reply data pulverizer <data.pulverizer gmail.com> writes:
On Sunday, 2 October 2022 at 16:44:25 UTC, Paul Backus wrote:
 It's because `writeln` is copying the object, and each of the 
 copies is being destroyed. If you add a copy constructor to 
 your example, you can see it happening:
 ...
I thought something like this could be happening in my original implementation and tried implementing a copy constructor using this reference https://dlang.org/spec/struct.html#struct-copy-constructor but it did not work. Both your's and the manual's suggestion works for my baby example but not for my actual code. Any reason why this could be?
Oct 02 2022
parent reply data pulverizer <data.pulverizer gmail.com> writes:
On Sunday, 2 October 2022 at 17:19:55 UTC, data pulverizer wrote:
 Any reason why this could be?
Sorry I'll need to implement all the overloaded copy constructors and see if that fixes it.
Oct 02 2022
next sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 10/2/22 10:28, data pulverizer wrote:
 On Sunday, 2 October 2022 at 17:19:55 UTC, data pulverizer wrote:
 Any reason why this could be?
What I noticed first in your original code was that it would be considered buggy because it was not considering copying. Every struct that does something in its destructor should either have post-blit (or copy constructor) defined or simpler, disallow copying altogether. That's what I did here: https://github.com/acehreli/alid/blob/main/cached/alid/cached.d#L178 disable this(this); I think disabling copy constructor was unnecessary but I did that as well: disable this(ref const(typeof(this))); The issue remains and bothers me as well. I think writeln copies objects because D disallows references to rvalue. We couldn't print rvalues if writeln insisted on 'ref'. Or, rvalues would be copied anyway if we used 'auto ref'. Hence the status quo...
 Sorry I'll need to implement all the overloaded copy constructors and
 see if that fixes it.
The best solution I know is to disable copying and printing not the object but an explicit string representation of it: Added: disable this(this); Added (there are better ways of doing the same e.g. using a 'sink' parameter): string toString() const { import std.format : format; return format!"id: %s"(id); } Called toString: writeln("MyObject: ", obj.toString); Ali
Oct 02 2022
parent data pulverizer <data.pulverizer gmail.com> writes:
On Sunday, 2 October 2022 at 17:51:59 UTC, Ali Çehreli wrote:
 What I noticed first in your original code was that it would be 
 considered buggy because it was not considering copying. Every 
 struct that does something in its destructor should either have 
 post-blit (or copy constructor) defined or simpler, disallow 
 copying altogether.
Thanks for the advice, for a while now I didn't know what was creating the issue. The code I'm running is my D connector to the R API and for ages I didn't know where the multiple destructor calls to allow an object to be garbage collected by the R API was coming from, and it was breaking the whole thing. I think I'll have to play it by ear whether to disable the copy constructor altogether or to use it now it is working. Thanks both of you.
Oct 02 2022
prev sibling parent reply data pulverizer <data.pulverizer gmail.com> writes:
On Sunday, 2 October 2022 at 17:28:51 UTC, data pulverizer wrote:
 Sorry I'll need to implement all the overloaded copy 
 constructors and see if that fixes it.
I've got it, something weird happened to my copy constructor. This was my original attempt and was ignored (didn't run in the copy constructor): ``` this(T)(ref return scope T original) if(is(T == RVector!(Type))) { //... code ... } ``` But this now works: ``` this(ref return scope RVector!(Type) original) { //... code ... } ``` No idea why. `Type` is a template parameter of the object.
Oct 02 2022
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 10/2/22 10:55, data pulverizer wrote:
 On Sunday, 2 October 2022 at 17:28:51 UTC, data pulverizer wrote:
 Sorry I'll need to implement all the overloaded copy constructors and
 see if that fixes it.
I've got it, something weird happened to my copy constructor. This was my original attempt and was ignored (didn't run in the copy constructor): ``` this(T)(ref return scope T original) if(is(T == RVector!(Type))) { //... code ... } ```
I've just tested. That is used only for explicit constructor syntax: auto b = RVector!int(a); // templatized
 But this now works:


 ```
 this(ref return scope RVector!(Type) original)
 {
      //... code ...
 }
 ```
That one works for both syntaxes: auto b = RVector!int(a); // templatized auto c = a; // non-templatized Certainly confusing and potentially a bug... :/
 No idea why. `Type` is a template parameter of the object.
Minor convenience: You can replace all RVector!(Type) with just RVector in the implementation of RVector because the name of the struct template *is* that specific instantiation of it: struct RVector(Type) { // RVector below means RVector!Type: this(ref return scope RVector original) { // ... } } Ali
Oct 02 2022
next sibling parent data pulverizer <data.pulverizer gmail.com> writes:
On Sunday, 2 October 2022 at 18:24:51 UTC, Ali Çehreli wrote:
 I've just tested. That is used only for explicit constructor 
 syntax ...
Many thanks. Knowledgeable as always!
Oct 02 2022
prev sibling parent Paul Backus <snarwin gmail.com> writes:
On Sunday, 2 October 2022 at 18:24:51 UTC, Ali Çehreli wrote:
 On 10/2/22 10:55, data pulverizer wrote:
 ```
 this(T)(ref return scope T original)
 if(is(T == RVector!(Type)))
 {
      //... code ...
 }
 ```
I've just tested. That is used only for explicit constructor syntax: auto b = RVector!int(a); // templatized
 But this now works:


 ```
 this(ref return scope RVector!(Type) original)
 {
      //... code ...
 }
 ```
That one works for both syntaxes: auto b = RVector!int(a); // templatized auto c = a; // non-templatized Certainly confusing and potentially a bug... :/
It's a bug in the documentation. https://issues.dlang.org/show_bug.cgi?id=23382 https://github.com/dlang/dlang.org/pull/3427
Oct 02 2022
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 10/2/22 12:21 PM, data pulverizer wrote:
 I've noticed that `writeln` calls the destructor of a struct multiple 
 times and would like to know how to stop this from happening. It has 
 become a very serious problem when working with objects that have memory 
 management external to D.
I know you already solved the problem, but just for future reference, if you use something like `RefCounted`, you can avoid copying and destruction until everyone is done with the object. This is how my io library works, the IO objects are non-copyable, and you wrap them in `RefCounted` if you want to copy them. -Steve
Oct 03 2022
parent data pulverizer <data.pulverizer gmail.com> writes:
On Monday, 3 October 2022 at 11:08:00 UTC, Steven Schveighoffer 
wrote:
 On 10/2/22 12:21 PM, data pulverizer wrote:
 I've noticed that `writeln` calls the destructor of a struct 
 multiple times and would like to know how to stop this from 
 happening. It has become a very serious problem when working 
 with objects that have memory management external to D.
I know you already solved the problem, but just for future reference, if you use something like `RefCounted`, you can avoid copying and destruction until everyone is done with the object. This is how my io library works, the IO objects are non-copyable, and you wrap them in `RefCounted` if you want to copy them. -Steve
Just seen this. Nice one. The docs link: https://dlang.org/library/std/typecons/ref_counted.html
Oct 04 2022