Thread overview
Adding empty static this() causes exception
Sep 12, 2017
Joseph
Sep 12, 2017
Moritz Maxeiner
Sep 12, 2017
Joseph
Sep 12, 2017
Moritz Maxeiner
Sep 12, 2017
nkm1
Sep 12, 2017
lobo
Sep 13, 2017
Biotronic
Sep 12, 2017
Biotronic
Sep 12, 2017
Biotronic
September 12, 2017
I have two nearly duplicate files I added a static this() to initialize some static members of an interface.

On one file when I add an empty static this() it crashes while the other one does not.

The exception that happens is
Cyclic dependency between module A and B.

Why does this occur on an empty static this? Is it being ran twice or something? Anyway to fix this?

Seriously, simply adding static this() { } to module B crashes the program ;/ module A and module B both import each other because there are types that they need to share but that is all(one uses an enum of the other and vice versa).


September 12, 2017
On Tuesday, 12 September 2017 at 09:11:20 UTC, Joseph wrote:
> I have two nearly duplicate files I added a static this() to initialize some static members of an interface.
>
> On one file when I add an empty static this() it crashes while the other one does not.
>
> The exception that happens is
> Cyclic dependency between module A and B.
>
> Why does this occur on an empty static this? Is it being ran twice or something? Anyway to fix this?

The compiler errors because the spec states [1]

>> Each module is assumed to depend on any imported modules being statically constructed first

, which means two modules that import each other and both use static construction have no valid static construction order.

One reason, I think, why the spec states that is because in theory it would not always be possible for the compiler to decide the order, e.g. when executing them changes the ("shared") execution environment's state:

---
module a;
import b;

static this()
{
    // Does something to the OS state
    syscall_a();
}
---

---
module b;
import a;

static this()
{
    // Also does something to the OS state
    syscall_b();
}
---

The "fix" as I see it would be to either not use static construction in modules that import each other, or propose a set of rules for the spec that define a always solvable subset for the compiler.

[1] https://dlang.org/spec/module.html#order_of_static_ctor


September 12, 2017
On Tuesday, 12 September 2017 at 09:11:20 UTC, Joseph wrote:
> I have two nearly duplicate files I added a static this() to initialize some static members of an interface.
>
> On one file when I add an empty static this() it crashes while the other one does not.
>
> The exception that happens is
> Cyclic dependency between module A and B.
>
> Why does this occur on an empty static this? Is it being ran twice or something? Anyway to fix this?
>
> Seriously, simply adding static this() { } to module B crashes the program ;/ module A and module B both import each other because there are types that they need to share but that is all(one uses an enum of the other and vice versa).

https://dlang.org/spec/module.html#order_of_static_ctor

"Cycles (circular dependencies) in the import declarations are allowed as long as not both of the modules contain static constructors or static destructors. Violation of this rule will result in a runtime exception."

So if you have a static this() in both A and B, and A imports B and B imports A, you will get this error message. You can pass --DRT-oncycle=ignore to the program to hide the problem, but a better solution is to find a way to live with fewer static this()s.

The reason this exception is thrown is that one module's static this() might otherwise depend on another module's static this that depends on the first module again, and there is no good way to check if it actually depends or just potentially.

--
  Biotronic
September 12, 2017
The simplest example of a cycle is probably this:

module A;
import B;

int n1 = 17;
static this() {
    n1 = n2;
}

//
module B;
import A;

int n2 = 42;
static this() {
    n2 = n1;
}

What's the value of n1 and n2 after module constructors are run? Since both module constructors can run arbitrary code, it's impossible to prove in the general case whether one of them depends on the other.

--
  Biotronic
September 12, 2017
On Tuesday, 12 September 2017 at 10:08:11 UTC, Moritz Maxeiner wrote:
> On Tuesday, 12 September 2017 at 09:11:20 UTC, Joseph wrote:
>> I have two nearly duplicate files I added a static this() to initialize some static members of an interface.
>>
>> On one file when I add an empty static this() it crashes while the other one does not.
>>
>> The exception that happens is
>> Cyclic dependency between module A and B.
>>
>> Why does this occur on an empty static this? Is it being ran twice or something? Anyway to fix this?
>
> The compiler errors because the spec states [1]
>
>>> Each module is assumed to depend on any imported modules being statically constructed first
>
> , which means two modules that import each other and both use static construction have no valid static construction order.
>
> One reason, I think, why the spec states that is because in theory it would not always be possible for the compiler to decide the order, e.g. when executing them changes the ("shared") execution environment's state:
>
> ---
> module a;
> import b;
>
> static this()
> {
>     // Does something to the OS state
>     syscall_a();
> }
> ---
>
> ---
> module b;
> import a;
>
> static this()
> {
>     // Also does something to the OS state
>     syscall_b();
> }
> ---
>
> The "fix" as I see it would be to either not use static construction in modules that import each other, or propose a set of rules for the spec that define a always solvable subset for the compiler.
>
> [1] https://dlang.org/spec/module.html#order_of_static_ctor

The compiler shouldn't arbitrarily force one to make arbitrary decisions that waste time and money.

My solution was to turn those static this's in to functions and simply call them at at the start of main(). Same effect yet doesn't crash. The compiler should only run the static this's once per module load anyways, right? If it is such a problem then some way around it should be included: @force static this() { } ? The compiler shouldn't make assumptions about the code I write and always choose the worse case, it becomes an unfriendly relationship at that point.
September 12, 2017
On Tuesday, 12 September 2017 at 19:59:52 UTC, Joseph wrote:
> On Tuesday, 12 September 2017 at 10:08:11 UTC, Moritz Maxeiner wrote:
>> On Tuesday, 12 September 2017 at 09:11:20 UTC, Joseph wrote:
>>> I have two nearly duplicate files I added a static this() to initialize some static members of an interface.
>>>
>>> On one file when I add an empty static this() it crashes while the other one does not.
>>>
>>> The exception that happens is
>>> Cyclic dependency between module A and B.
>>>
>>> Why does this occur on an empty static this? Is it being ran twice or something? Anyway to fix this?
>>
>> The compiler errors because the spec states [1]
>>
>>>> Each module is assumed to depend on any imported modules being statically constructed first
>>
>> , which means two modules that import each other and both use static construction have no valid static construction order.
>>
>> One reason, I think, why the spec states that is because in theory it would not always be possible for the compiler to decide the order, e.g. when executing them changes the ("shared") execution environment's state:
>>
>> ---
>> module a;
>> import b;
>>
>> static this()
>> {
>>     // Does something to the OS state
>>     syscall_a();
>> }
>> ---
>>
>> ---
>> module b;
>> import a;
>>
>> static this()
>> {
>>     // Also does something to the OS state
>>     syscall_b();
>> }
>> ---
>>
>> The "fix" as I see it would be to either not use static construction in modules that import each other, or propose a set of rules for the spec that define a always solvable subset for the compiler.
>>
>> [1] https://dlang.org/spec/module.html#order_of_static_ctor
>
> The compiler shouldn't arbitrarily force one to make arbitrary decisions that waste time and money.

My apologies, I confused compiler and runtime when writing that reply (the detection algorithm resulting in your crash is built into druntime).
The runtime, however, is compliant with the spec on this AFAICT.

> The compiler should only run the static this's once per module load anyways, right?

Static module constructors are run once per module per thread [1] (if you want once per module you need shared static module constructors).

> If it is such a problem then some way around it should be included: @force static this() { } ?

The only current workaround is what Biotronic mentioned: You can customize the druntime cycle detection via the --DRT-oncycle command line option [2].

> The compiler shouldn't make assumptions about the code I write and always choose the worse case, it becomes an unfriendly relationship at that point.

If your point remains when replacing 'compiler' with 'runtime': It makes no assumptions in the case you described, it enforces the language specification.

[1] https://dlang.org/spec/module.html#staticorder
[2] https://dlang.org/spec/module.html#override_cycle_abort
September 12, 2017
On Tuesday, 12 September 2017 at 19:59:52 UTC, Joseph wrote:
> The compiler shouldn't arbitrarily force one to make arbitrary decisions that waste time and money.

You might want to educate yourself about arbitrary decisions that waste time and money: https://isocpp.org/wiki/faq/ctors#static-init-order

>
> My solution was to turn those static this's in to functions and simply call them at at the start of main(). Same effect yet doesn't crash. The compiler should only run the static this's once per module load anyways, right? If it is such a problem then some way around it should be included: @force static this() { } ?

There is a way, turn static this's into functions and simply call them at the start of main().

> The compiler shouldn't make assumptions about the code I write and always choose the worse case, it becomes an unfriendly relationship at that point.

If you want C++, you know where to find it, although I wouldn't exactly call it "friendly".


September 12, 2017
On Tuesday, 12 September 2017 at 19:59:52 UTC, Joseph wrote:
> On Tuesday, 12 September 2017 at 10:08:11 UTC, Moritz Maxeiner wrote:
>> [...]
>
> The compiler shouldn't arbitrarily force one to make arbitrary decisions that waste time and money.
>
> My solution was to turn those static this's in to functions and simply call them at at the start of main(). Same effect yet doesn't crash. The compiler should only run the static this's once per module load anyways, right? If it is such a problem then some way around it should be included: @force static this() { } ? The compiler shouldn't make assumptions about the code I write and always choose the worse case, it becomes an unfriendly relationship at that point.

Solution is to redesign so there is no cycle because it is brittle and generally due to poor architecture. Like goto cyclic dependencies are occasionally useful, rarely necessary and avoid by default.

bye,
lobo
September 13, 2017
On Tuesday, 12 September 2017 at 19:59:52 UTC, Joseph wrote:
> The compiler shouldn't arbitrarily force one to make arbitrary decisions that waste time and money.

Like having a type system? Having to do *cast(int*)&s to interpret a string as an int isn't strictly necessary, and wastes dev time when they have to type extra letters.

Throwing an exception when there are import cycles that may cause problems is absolutely the correct thing to do. It's a choice between hard-to-figure-out errors that may depend on the order in which files were passed to the linker, and getting an exception before you've even started running your tests. If we could get this message at compile-time, that would of course be better, but that cannot be done in the general case due to separate compilation. If you want such a message in the cases where it is possible, feel free to create a pull request.

It would be possible to add a way to say 'ignore cycles for this module ctor', but as has been pointed out, cycles are generally a symptom of poor architecture, are brittle, and can almost always be avoided (and when they can't, there's a way around that too).

--
  Biotronic