On Sunday, 4 June 2017 at 09:04:14 UTC, Jonathan M Davis wrote:

if throwing in a destructor is considered a runtime error, perhaps another valid enhancement would be to statically disallow throwing Exceptions in destructors, i.e. *require* them be nothrow?..

My initial reaction would be that destructors should always be nothrow, though I vaguely recall there being some reason why it was supposed to be nice that destructors in D could cleanly deal with exceptions. And remember that when we're talking about rt_finalize, we're talking about finalizers, not destructors in general. When a destructor is in a GC heap-allocated object, it's treated as a finalizer and may or may not be run (since the object may or may not be collected),

It doesn't matter. The only thing that matters is that it may be run, and therefore rt_finalize has to count on that. And it sort of does, at the moment, by assuming the worst possible combination of attributes. Problem is, with current language rules, it cannot be any other way, as the runtime doesn't carry any information about attributes of finalized object, or the context in which finalization takes place (i.e. is it within a @safe function?), which, sadly, makes unsafe code silently executable in a safe context, in direct contradiction to language guarantees.

whereas when a destructor is on an object that's on the stack, it's really a destructor. So, while they use the same syntax,

It's worse than that. There are two "destructors": __xdtor that calls destructors of RAII members, and, on classes, __dtor that actually calls ~this() for the class. But only that class, not it's ancestors or descendants. Such segregation is, as it turns out, as useful as it is unwieldy.

and in the case of a struct, the same function could be either a destructor or a finalizer depending on where the struct is declared, they're not quite the same thing. And destroy muddies the water a bit, because it then explicitly calls the finalizer on a class, whereas it would normally be the GC that does it (and while calling GC-related functions in a finalizer is forbidden when called by the GC, it's fine when called via destroy, since the GC is then not in the middle of a collection).

So, I don't know whether it would be reasonable to require that destructors be nothrow. Certainly, it's _more_ likely for it to be reasonable for destructors on classes to be nothrow, since classes always live on the heap (and are thus finalizers) unless you're playing games with something like std.typecons.scoped, but I'd have to study the matter quite a bit more to give a properly informed answer as to whether it would be reasonable to require that all destructors be nothrow.

Scoped is not necessary. Classes may not necessarily exist in the GC heap, thanks to custom allocators and emplace(). But because the language does not enforce propagation of destructor attributes, destroy() is @system and not nothrow, which spills out into user code that would otherwise take advantage of static inference. Unfortunately, right now making it any other would impose certain restrictions on classes without real language support, and that is... scary.

Reply via email to