Re: class destructors must be @disabled?

2022-05-19 Thread bauss via Digitalmars-d-learn

On Wednesday, 18 May 2022 at 19:58:09 UTC, Ali Çehreli wrote:


Hmm. Perhaps the guideline should be "all destructors must be 
@nogc".


Ali


It should probably just default to that and with no exception, 
since you will never end up in a situation where you don't want 
@nogc for a destructor.


At least I can't imagine one, and if there is one then it's 
probably due to another design issue in one's program.


Re: class destructors must be @disabled?

2022-05-18 Thread Steven Schveighoffer via Digitalmars-d-learn

On 5/18/22 3:58 PM, Ali Çehreli wrote:


Hmm. Perhaps the guideline should be "all destructors must be @nogc".


That one I agree with.

-Steve


Re: class destructors must be @disabled?

2022-05-18 Thread Ali Çehreli via Digitalmars-d-learn

On 5/18/22 12:45, Steven Schveighoffer wrote:

> Not cleaning it up because you're afraid of destructors is not the 
answer.


Fine. The GC allocation issue remains. It looks like one of the 
following should be observed:


a) @disable ~this() for all classes so that we are safe from the 
destructors of otherwise-well-written struct members as well. In 
general, this option requires a cleanup function for the class, which 
should be called at an opportune moment before its memory is freed by 
the GC.


b) Consider GC.inFinalizer() in every struct destructor that has any 
chance of being used in a class. This may not work for all structs (see 
below).


c) Extend the "do not allocate GC memory in the destructor" guideline to 
all structs.


Do not allocate memory in such conditions, which may be problematic 
because my example would make every struct object expensive by holding 
on to a string prepared before hand to be used in the destructor:


struct S {
  int id;
  string closingMessage;   // <-- Expensive

  this(int id) {
this.id = id;
this.closingMessage = format!"%s signing off"(id);
  }

  ~this() {
// No allocation in the destructor; good.
closeConnection(closingMessage);
  }
}

The non-expensive solution is to use a @nogc solution like sprintf?

Hmm. Perhaps the guideline should be "all destructors must be @nogc".

Ali



Re: class destructors must be @disabled?

2022-05-18 Thread Steven Schveighoffer via Digitalmars-d-learn

On 5/18/22 2:58 PM, H. S. Teoh wrote:

On Wed, May 18, 2022 at 02:35:00PM -0400, Steven Schveighoffer via 
Digitalmars-d-learn wrote:
[...]

No. Class destructors are for cleaning up non-GC resources. As long as
you stick to those, you can safely run them.

Structs that get put into classes have to run their destructors
properly, otherwise, you will have horrible inconsistencies.

For instance, I would not want to disable the destruction of a
RefCounted struct inside a class.

[...]

So if the user runs your program with --DRT-gcopt=cleanup:none and you
happen to have a RefCounted struct inside a GC-allocated class, then
you're screwed?


I approach it from a different way. Let's say I'm writing a File class, 
and it has a file descriptor inside it.


It's not me that's deciding when to clean up the object. All I want to 
do as the *library author* is to clean up the resource *I* opened, when 
someone is cleaning my object up. In other words, I don't care how you 
destroy it, GC, synchronously, etc, when you clean up the class 
instance, I will clean up my mess. That's just good lifetime management.


Not cleaning it up because you're afraid of destructors is not the answer.

That being said, I think it's a reasonable position for you to not 
support running your program with that DRT option (obviously, we do rely 
on the GC to clean up some things).


-Steve


Re: class destructors must be @disabled?

2022-05-18 Thread Ali Çehreli via Digitalmars-d-learn

On 5/18/22 11:35, Steven Schveighoffer wrote:

> Structs that get put into classes have to run their destructors
> properly, otherwise, you will have horrible inconsistencies.

Does that suggest a different guideline: "Careful with structs in 
classes." That goes against orthogonality (independence). I should not 
care where a struct type is used. Or, the way to use it properly should 
be handled by the struct. (Continuing below.)


> You can use the GC.inFinalizer to check if you are concerned about using
> the GC in your struct dtors.

Is that the guideline for most usability then? struct destructors must 
check for GC.inFinalizer because they may be used in a class. This 
doesn't sound useful either.


Perhaps my struct example should have allocated the "farewell" string 
before the destructor. And then this renders struct destructors almost 
like class destructors: Don't allocate in the destructor.


Something is fishy here. :)

Ali



Re: class destructors must be @disabled?

2022-05-18 Thread H. S. Teoh via Digitalmars-d-learn
On Wed, May 18, 2022 at 02:35:00PM -0400, Steven Schveighoffer via 
Digitalmars-d-learn wrote:
[...]
> No. Class destructors are for cleaning up non-GC resources. As long as
> you stick to those, you can safely run them.
> 
> Structs that get put into classes have to run their destructors
> properly, otherwise, you will have horrible inconsistencies.
> 
> For instance, I would not want to disable the destruction of a
> RefCounted struct inside a class.
[...]

So if the user runs your program with --DRT-gcopt=cleanup:none and you
happen to have a RefCounted struct inside a GC-allocated class, then
you're screwed?


T

-- 
Three out of two people have difficulties with fractions. -- Dirk Eddelbuettel


Re: class destructors must be @disabled?

2022-05-18 Thread H. S. Teoh via Digitalmars-d-learn
On Wed, May 18, 2022 at 11:02:01AM -0700, Ali Çehreli via Digitalmars-d-learn 
wrote:
[...]
> HORROR:
> 
> Now, a big one that I've just realized.
> 
> - The two guidelines above (the "don't touch class members" one and
> the "don't allocate" one) miss an important fact that they apply
> recursively even to struct members of classes. For example, structs
> that are used as members of a class cannot allocate memory in their
> destructors either.
[...]
> Of course, we still should and do have the power to shape our programs
> any way we want but I think '@disable ~this();' should be added to
> classes as a general rule unless the programmer knows it will work
> otherwise.
[...]

I remember years ago, Andrei proposed that we remove class dtors (or
finalizers, whatever) from the language altogether.  That may be a bit
extreme, but it reflects the general idea you wrote here.

One observation about class dtors is, if they're useless for cleaning up
resources (since we can't rely on them being run at all, and we're not
allowed to touch anything referenced by the class that may have already
been collected by the GC, and we're not allowed to do anything that may
trigger a GC allocation), then *what practical use do they serve at
all*?!  At this point they're just some vestigial construct that are not
useful for all practical intents and purposes anymore.

The one big caveat, of course, is emplaced classes and classes whose
allocation is being managed by something other than the (default) GC. If
a class is stack-allocated, for example, the dtor could meaningfully do
something useful like free resources upon going out of scope.  IOW,
class dtors are useless by default, and only useful in non-default
usage. :-P


T

-- 
Obviously, some things aren't very obvious.


Re: class destructors must be @disabled?

2022-05-18 Thread Steven Schveighoffer via Digitalmars-d-learn

On 5/18/22 2:02 PM, Ali Çehreli wrote:

Of course, we still should and do have the power to shape our programs 
any way we want but I think '@disable ~this();' should be added to 
classes as a general rule unless the programmer knows it will work 
otherwise.


What do you think?


No. Class destructors are for cleaning up non-GC resources. As long as 
you stick to those, you can safely run them.


Structs that get put into classes have to run their destructors 
properly, otherwise, you will have horrible inconsistencies.


For instance, I would not want to disable the destruction of a 
RefCounted struct inside a class.


You can use the GC.inFinalizer to check if you are concerned about using 
the GC in your struct dtors.


-Steve


class destructors must be @disabled?

2022-05-18 Thread Ali Çehreli via Digitalmars-d-learn
I am writing these in view of class objects commonly being constructed 
as GC-owned objects.


GUIDELINES:

We've seen the following points mentioned in these forums.

- (This one is just a complication.) Classes don't have destructors 
anyway; they should be called finalizers.


- Don't allocate memory from the GC in a class destructor. (For example, 
writeln may be fine with simple types but writefln or format would not be.)


- Don't touch any GC-owned member in a class destructor because that 
member may have already been finalized.


SOME FACTS:

- Class destructors are not guarenteed to be executed. And this cannot 
be controlled by the programmer. For example, the user of the program 
can pass the following command line option and the GC will not execute 
class destructors:


  $ myprogram "--DRT-gcopt=cleanup:none" ...

(The following example will run fine when started that way.)

- Even if the destructor is executed by the GC, when it is executed 
exactly is naturally unspecified ("sometime in the future"). For that 
reason, it does not make sense to leave important responsibilities to 
class destructors like closing a File. Not only the file may not be 
closed, you may be exceeding resources that the OS provides by holding 
on to them for too long.


HORROR:

Now, a big one that I've just realized.

- The two guidelines above (the "don't touch class members" one and the 
"don't allocate" one) miss an important fact that they apply recursively 
even to struct members of classes. For example, structs that are used as 
members of a class cannot allocate memory in their destructors either.


The following program ends with core.exception.InvalidMemoryOperationError:

import std.format;

void closeConnection(string s) {
}

struct S {
  int id;

  ~this() {
closeConnection(format!"%s signing off"(id));
  }
}

class C {
  S s;

  this(int id) {
s = S(id);
  }

  // @disable ~this();
}

void main() {
  // Just 1 is sufficient to show the error in Ali's environment.
  enum N = 1;
  foreach (i; 0 .. N) {
auto c = new C(i);
  }
}

Note how the struct is written in good faith and the class is obeying 
all the guidelines (by not even defining a destructor). I think the 
problem is the missed guideline that is on the subject line: classes 
should @disable their destructors. The program above will work fine when 
that line is uncommented.


Of course, we still should and do have the power to shape our programs 
any way we want but I think '@disable ~this();' should be added to 
classes as a general rule unless the programmer knows it will work 
otherwise.


What do you think?

Ali