August 24, 2018
On 24/08/2018 1:57 PM, Shachar Shemesh wrote:
> Consider the following line from the weka code base:
> 
>          _trustedData.opIndex(diskIdx) &= NotBitmap(toDistrust);
> 
> That's strange. Why didn't Shachar just do?
> 
>          _trustedData[diskIdx] &= NotBitmap(toDistrust);
> 
> Answer: Because the compiler decided that it needs to call _trustedData.opIndexAssign, and then failed the compilation because it has none. There is no way that that is a bug in the code. This is a compiler bug (maybe since fixed. This is fairly old code).

From now on, please report it as-is and tag it as e.g. unknown_weka or something. Give us a report once a month or so, on what is open.

It'll be easier to figure out what is going on if we can see the symptom. Even if it isn't the problem its a step in the right direction.
August 24, 2018
On 24/08/2018 6:27 AM, Abdulhaq wrote:
> On Thursday, 23 August 2018 at 09:51:43 UTC, rikki cattermole wrote:
>>> Good luck getting W&A to agree to it, especially when there is yet another "critical D opportunity" on the table ;)
>>
>> No. They have power for as long as we the community say that they do.
>> We are at the point where they need a check and balance to keep everybody going smoothly. And I do hope that they listen to us before somebody decides its forkin' time.
> 
> No fork of D can be successful, it won't have the manpower, skills or willpower to draw on. Even with W and A it's already short.
> 
> 'Threatening' W and A with a fork is an empty threat that just p***es them off. Bad move on your part.

Oh relax. It isn't close to a threat and they themselves have said what I did that their power only exists as long as the community says so in the past.

August 24, 2018
On Thursday, 23 August 2018 at 23:27:51 UTC, Walter Bright wrote:
> D deals with it via "chained exceptions", which is terrifyingly difficult to understand. If you believe it is understandable, just try to understand the various devious test cases in the test suite.

I don't think that assessment is accurate. Yes, I ported EH to a few new targets and wrote the first correct implementation of exception chaining for LDC, so I'm probably a couple of standard deviations off the average D user in that regard. But said average D user doesn't care about most of the nuances of this problem, like the details of implementing exception chaining without allocating too much, or which exceptions take precedence in various intentionally twisted test cases.

What they do care about is that the fact that an error has occurred is propagated sensibly in all cases without requiring extra attention, and that information as to the origin is not lost (hence chaining rather than just replacement). Heck, the fact that we still don't have default backtrace handlers that consistently work on all platforms is probably a much bigger problem than the minutiae of exception chaining behaviour.

All this is not to say that nothrow constructors aren't a good idea, though.

———

> 1) They are expensive, adding considerable hidden bloat in the form of finally blocks, one for each constructing field. These unwinding frames defeat optimization. […]

This cost is inherent to the problem, at least as long as exceptions are used to represent the error conditions in question in the first place. Whether the potentially throwing operations are performed in the constructor or in another method, either way the object will need to be destructed and all preceding initialisation steps undone when an error occurs.

> 2) The presence of constructors that throw makes code hard to reason about. (I concede that maybe that's just me.) I like looking at code and knowing the construction is guaranteed to succeed.

This might be just you indeed. How are constructors different than other method calls in that regard? (Granted, constructors can be called implicitly in C++.)

> Somehow, I've been able to use C++ for decades without needing throwing constructors.

How much of what would be considered "modern" C++ code have you written in that time, as opposed to C-with-classes style?

> It [having a separate initialisation method]'s still RAII

One might wonder about the acronym then, but whether your example should be called RAII is an entirely different debate, and one I'm not particularly interested in (I've always thought something like "Resource Release Is Destruction" or just "Scope-based resource management" would be a better name anyway).

> You might argue "my code cannot handle that extra check in the destructor."

It's not just one extra check in the destructor. It's an extra check in every member function, to throw an exception if called on an object that has not been initialised.

You might argue that these should be errors/assertions instead, and hence are not as expensive. True, but this points to another problem: Splitting up the initialisation procedure invites a whole class of bugs that would otherwise be syntactically impossible to write.

There is considerable theoretical and practical beauty to the idea that as an object is accessible, it is in a valid state. (Linear/affine types, cf. Rust, show the power of this notion extended to ownership.) RAII in the conventional sense (acquisition in the constructor) effectively provides a way to encode 1 bit of typestate. By blurring the line between object initialisation and the phase where it is fully initialised, that descriptive power is lost.

> […] (for me) the inherently unintuitive nature of a constructor that tries to do far too much.

I suppose our opinions just differ here then. To me, this is exactly what I intuitively expect a constructor to do: Create an instance of an object in a normal state. Drawing the line anywhere else, for example (implicitly) allocating memory but not initialising it to a fully usable state, seems artificial to me.

> 3) Much of the utility of throwing constructors in C++ comes from "what if the constructor fails to allocate memory". In D, out of memory errors are fatal, no recovery is necessary.

This is unrelated to the discussion at hand, but std.experimental.allocator does allow handle allocation failure, and with good reason: It doesn't make sense to abort the program when a fixed-size local cache or a static global object pool (common in embedded programming) is exhausted.

 — David
August 24, 2018
On Thursday, 23 August 2018 at 23:06:00 UTC, Ethan wrote:
> Is that actually true, or would handling exceptions within the constructor allow one to initialise the object to an invalid state and thus still follow RAII paradigms correctly?

If you end up needing to check for that uninitialised state in all the other member functions for correctness (to avoid operating on invalid pointers/handles/…), that's a imho bastardisation of RAII that destroys most of the benefits.

Also, how do you inform the client code that something went wrong internally? Most likely, you'd want to construct the objects internally and pass them into the constructor to get around that; hence the composability argument.

> The amount of bugs and pain I've had to deal with in production code because of throwing constructors makes me lean more towards Walter's viewpoint here.

What was the root cause of these issues?

> Why can't RAII objects do the same if it performs operations it *knows* throw exceptions?

Apart from the above argument, this would require making all constructors implicitly nothrow to avoid degrading into faith-based programming.

 — David
August 23, 2018
On Thursday, August 23, 2018 7:01:41 PM MDT Mike Franklin via Digitalmars-d wrote:
> On Friday, 24 August 2018 at 00:58:35 UTC, Guillaume Piolat wrote:
> > D programs tend to use the C runtime directly, and quite a lot of it: https://github.com/search?l=D&q=%22import+core.stdc%22&type=Code
>
> I know.  They should get that from https://github.com/D-Programming-Deimos/libc or perhaps even Dub.

Unless you're trying to argue for folks dropping Phobos, that's just not going to fly. Phobos uses libc heavily, and it really can't do what it needs to do without it (e.g. file operations). Divorcing druntime from libc may help folks focused on embedded development and who don't want to use Phobos, but for most D programs, it really doesn't provide any real benefit to try to make druntime not use libc. So, while such an effort may provide some benefits, I don't see how it could really be for anything other than a niche part of the community.

- Jonathan M Davis



August 24, 2018
On Friday, 24 August 2018 at 04:12:42 UTC, Jonathan M Davis wrote:

> Unless you're trying to argue for folks dropping Phobos, that's just not going to fly. Phobos uses libc heavily, and it really can't do what it needs to do without it (e.g. file operations). Divorcing druntime from libc may help folks focused on embedded development and who don't want to use Phobos, but for most D programs, it really doesn't provide any real benefit to try to make druntime not use libc. So, while such an effort may provide some benefits, I don't see how it could really be for anything other than a niche part of the community.

It's not a problem for Phobos to depend on the C standard library.  My goals have to do with making D, the language, freestanding (a.k.a nimble-D).

If and when druntime no longer depends on the C standard library, the bindings can be moved to a separate repository (e.g. Deimos).  A compatibility shim can also be created in a separate repository to forward `core.stdc` names to `c.std` or whatever name the new repository chooses. That compatibility shim could be marked deprecated in favor of the new name, and then many years down the line it can be removed (or kept, I don't care).  It then becomes part of the toolchain packaging process to add Deimos-libc and the compatibility shim to the dmd.conf file and include it in the distribution.  Users won't even know it happened.  Users's coding in D, the language (no Phobos), will no longer have to obtain a C toolchain to generate their binaries.

We're at least half a decade away from any of this, and there's a good chance it will never even happen, so don't sweat it.

And about niche use case.  They're niche right now because D doesn't provide good support for them, and noone's writing D software for them.  If I have my druthers, that's going to change, and those use case will become major considerations when making language design choices, and it will become obvious that C is a more of a liability than an asset.

Mike
August 24, 2018
On Thursday, 23 August 2018 at 06:34:04 UTC, Shachar Shemesh wrote:

> Here's the interesting question, though: is this *going* to happen?

I didn't know about the issue until you recently brought it up.

--
/Jacob Carlborg
August 24, 2018
On 24/08/18 05:33, Jonathan M Davis wrote:
> 
> Yeah. I've used RAII plenty in D without problems, but the fact remains that
> certain uses of it are very broken right now thanks to the constructor
> issue. I suspect that Shachar's as negative about this as he is in part
> because having RAII go wrong with the kind of low-level stuff Weka does
> would be a serious problem

Yes.

I will point out that I was never bit by this bug either. We found it while trying to figure out whether we want to start relying on destructors internally.

The thing is, when a destructor doesn't run, this costs you a *lot* of time in finding out why. We actually have stuff that is downright weird as a result of not trusting destructors.

That stuff is so weird, that for Mecca I essentially said I'm going to rely on them. Sadly, this means that this bug has become a bigger blocker than it was.

>> (Having throwing destructors is even worse, it's just madness. Although it
>> is allowed in C++, it doesn't actually work.)
> 
> Yeah. We probably should have required that destructors be nothrow and force
> destructor failures to be treated as Errors.

I'm sorry, but I'm not following your logic.

If you're willing to have an error raised by a destructor abort the whole program, isn't the C++ solution preferable (abort the program only on double errors, which hardly ever happens)?

Shachar
August 24, 2018
On Friday, 24 August 2018 at 01:57:03 UTC, Shachar Shemesh wrote:
> That's strange. Why didn't Shachar just do?
>
>         _trustedData[diskIdx] &= NotBitmap(toDistrust);
>
> Answer: Because the compiler decided that it needs to call _trustedData.opIndexAssign, and then failed the compilation because it has none. There is no way that that is a bug in the code. This is a compiler bug (maybe since fixed. This is fairly old code).
>
> So, where's the issue number, I hear you ask? There is none. This problem only happens inside the code base. Once I tried to isolate it, I couldn't reproduce.
>

Have you tried to use the excellent Dustmite tool? It's never failed to reduce a bug for me.
August 24, 2018
On Thursday, 23 August 2018 at 23:27:51 UTC, Walter Bright wrote:
> Back to throwing constructors.
>
> 1) They are expensive, adding considerable hidden bloat in the form of finally blocks, one for each constructing field. These unwinding frames defeat optimization. The concept of "zero-cost exception handling" is a bad joke. (Even Chandler Carruth stated that the LLVM basically gives up trying to optimize in the presence of exception handlers.) Herb Sutter has a recent paper out proposing an alternative, completely different, error handling scheme for C++ because of this issue.

Are you referring to http://wg21.link/P0709 ?
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19