www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Strange compiler error. Whose bug is that?

reply Oleksii Skidan <al.skidan gmail.com> writes:
Hi,

I get a strange error:

```
λ dub build
Performing "debug" build using D:\d\dmd2\windows\bin\dmd.exe for 
x86.
strange ~master: building configuration "application"...
source\app.d(24,18): Error: non-constant expression 
&[Particle(1.00000F, 1.00000F, 1.00000F), Particle(2.00000F, 
2.00000F, 1.00000F), Particle(3.00000F, 3.00000F, 1.00000F)][0]
source\app.d(25,18): Error: non-constant expression 
&[Particle(1.00000F, 1.00000F, 1.00000F), Particle(2.00000F, 
2.00000F, 1.00000F), Particle(3.00000F, 3.00000F, 1.00000F)][1]
source\app.d(24,18): Error: non-constant expression 
&[Particle(1.00000F, 1.00000F, 1.00000F), Particle(2.00000F, 
2.00000F, 1.00000F), Particle(3.00000F, 3.00000F, 1.00000F)][1]
source\app.d(25,18): Error: non-constant expression 
&[Particle(1.00000F, 1.00000F, 1.00000F), Particle(2.00000F, 
2.00000F, 1.00000F), Particle(3.00000F, 3.00000F, 1.00000F)][2]
source\app.d(24,18): Error: non-constant expression 
&[Particle(1.00000F, 1.00000F, 1.00000F), Particle(2.00000F, 
2.00000F, 1.00000F), Particle(3.00000F, 3.00000F, 1.00000F)][2]
source\app.d(25,18): Error: non-constant expression 
&[Particle(1.00000F, 1.00000F, 1.00000F), Particle(2.00000F, 
2.00000F, 1.00000F), Particle(3.00000F, 3.00000F, 1.00000F)][0]
D:\d\dmd2\windows\bin\dmd.exe failed with exit code 1.
```

when compiling the following code:

```
import std.stdio;

struct Particle {
     float x;
     float y;
     float invMass;

     this(float x, float y)
     {
         this.x = x;
         this.y = y;
         this.invMass = 1f;
     }
}

struct Constraint {
     Particle* x;
     Particle* y;

     float distance;

     this(ref Particle x, ref Particle y)
     {
         this.x = &x;
         this.y = &y;

         auto dx = x.x - y.x;
         auto dy = x.y - y.y;

         import std.math;
         this.distance = sqrt(dx*dx + dy*dy);
     }
}

interface Body {
     void tick();
}

class Triangle : Body {

     Particle[3] particles = [
         Particle(1, 1),
         Particle(2, 2),
         Particle(3, 3)
     ];

     Constraint[3] constraints;

     this()
     {
         constraints[0] = Constraint(particles[0], particles[1]);
         constraints[1] = Constraint(particles[1], particles[2]);
         constraints[2] = Constraint(particles[2], particles[0]);
     }

     void tick()
     {
         particles[0].x += 1;
     }
}

struct Game {
     Triangle player = new Triangle;

     void tick() {
         player.tick();
     }
}

void main()
{
     Game game;
     game.tick();
}
```

Is that my bug (highest probability here) or a compiler bug? I'm 
confused, since the following `main` function compiles 
successfully:

```
void main()
{
     Triangle player = new Triangle;
     player.tick();
}
```

as well as this code:

```

struct Game {
     Triangle player;

     this(Triangle player)
     {
         this.player = player;
     }

     void tick() {
         player.tick();
     }
}

Game makeGame()
{
     return Game(new Triangle);
}

void main()
{
     auto game = makeGame();
     game.tick();
}
```

What's wrong with the first code snippet?

Thanks in advance,
--
Oleksii
Jan 26 2018
parent reply thedeemon <dlang thedeemon.com> writes:
On Friday, 26 January 2018 at 21:17:14 UTC, Oleksii Skidan wrote:

 struct Game {
     Triangle player = new Triangle;
When you initialize a struct member like this, compiler tries to calculate the initial value and remember it as data, so each time such struct is constructed the data is just copied. Which means this data must be computable at compile time, however your Triangle constructor is using pointers to some values, these pointers will only be known at run time. This means you need to construct Triangles at run time, in Game constructor, not at compile time in this initialization syntax.
Jan 27 2018
parent reply Oleksii Skidan <al.skidan gmail.com> writes:
On Saturday, 27 January 2018 at 08:18:07 UTC, thedeemon wrote:
 On Friday, 26 January 2018 at 21:17:14 UTC, Oleksii Skidan 
 wrote:

 struct Game {
     Triangle player = new Triangle;
When you initialize a struct member like this, compiler tries to calculate the initial value and remember it as data, so each time such struct is constructed the data is just copied. Which means this data must be computable at compile time, however your Triangle constructor is using pointers to some values, these pointers will only be known at run time. This means you need to construct Triangles at run time, in Game constructor, not at compile time in this initialization syntax.
Got it. But are reference-types "computable" at compile time at all? Shouldn't they be relying on D runtime? To my understanding Triangle instantiation happens when Game constructor is called. I assume that D runtime has been initialized already, and thus there should be a valid GC and it should be fine to instantiate a reference-type. As well, if I'm wrong about Game constructor, then compiler generated errors are wrong and misleading. The compiler should be swearing at `Triangle player = new Triangle;`, or not? Thanks, -- Oleksii
Jan 27 2018
parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Saturday, January 27, 2018 09:35:05 Oleksii Skidan via Digitalmars-d-
learn wrote:
 On Saturday, 27 January 2018 at 08:18:07 UTC, thedeemon wrote:
 On Friday, 26 January 2018 at 21:17:14 UTC, Oleksii Skidan

 wrote:
 struct Game {

     Triangle player = new Triangle;
When you initialize a struct member like this, compiler tries to calculate the initial value and remember it as data, so each time such struct is constructed the data is just copied. Which means this data must be computable at compile time, however your Triangle constructor is using pointers to some values, these pointers will only be known at run time. This means you need to construct Triangles at run time, in Game constructor, not at compile time in this initialization syntax.
Got it. But are reference-types "computable" at compile time at all? Shouldn't they be relying on D runtime? To my understanding Triangle instantiation happens when Game constructor is called. I assume that D runtime has been initialized already, and thus there should be a valid GC and it should be fine to instantiate a reference-type. As well, if I'm wrong about Game constructor, then compiler generated errors are wrong and misleading. The compiler should be swearing at `Triangle player = new Triangle;`, or not?
I don't think that "computable" is really the best way to look at this. It's more an isssue of the value being carried over from compile time to runtime. But I'll try to explain it. You're clearly assuming that some stuff is happening at runtime that happens at compile time. Module-level variables, static variables, and member varibales which are directly initialized must all have their values known at compile time. If you have something like static int i = foo(); or struct S { int i = foo(); } then the compiler must know the value of i at compile time. As such, foo must be run at compile time so that the resulting value can be known and stored in the program. In the case of a member variable of a struct, that's in the init value of the struct (it's the same for classes except that you don't have access to it, since with classes, you always operate on references, never the class itself, and the class reference's init value is null; the underlying object still has an init value though). For module-level variables and static variables, the runtime may need to do some stuff on program or thread start-up to fill in the values, but they're all known at compile time. For member variables, the object is initialized to the type's init value when the object is created, and then if a constructor is used, it is run. In the case of S s; no constructor is run (s is just filled in with the init value), whereas with something like auto s = S("hello"); or auto c = new MyClass(42); the object is filled in with the init value and then a constructor is run. Either way, if you have struct S { int i = foo(); } foo was run at compile time and not rerun at runtime. Its result is part of S.init. If a value is set in the constructor, e.g. struct S { int i = foo(); string s; this(string s) { this.s = s; } } then that's done at runtime, but all of the direct initializions are done at compile time when determining the value of S.init. Given how all of this works, it's actually king of crazy that reference types work at all. Consider, struct S { auto arr = [1, 2, 3, 4]; } arr must be known at compile time, and yet are is just a pointer and a length pointing to heap memory. Heap memory isn't part of the executable. It's part of a specific run of the program. So, somehow, the value of the memory that arr refers to needs to be calculated at compile time and then reconstructed at runtime. The runtime has to recreate it in memory from the value that was known at compile time. It's not going to rerun any functions, so if you have struct S { int[] arr = foo(); } foo still has to have been run at compile time. The resulting value will need to have been stored somehow so that S.init can contain a normal dynamic array that points to heap memory at runtime. For the compiler to work like this with dynamic arrays, it had to have had work done specifically for dynamic arrays so that the runtime would know how to reconstruct them. That's not something that can easily be done for arbitrary types - and it hasn't even been done for some of the simple stuff, though what has been done has increased over time. e.g. struct S { int* i = new int(42); } will not compile. But for arbitrarily complex user-defined types, it gets far more complex. So, if you have something like struct S { auto myClass = new MyClass(42); } the compiler does not transfer the value of the class reference at compile time to runtime. In years past, I would have simply explained the compiler and runtime don't understand how to reconstruct a class like that (heck, the language doesn't even have a built-in way to copy a class - just its reference). However, a few years back, the compiler and runtime were actually improved improved enough that they're able to take a const or immutable class and reconstruct it at runtime. So, struct S { immutable myClass = new immutable MyClass(4); } would work. But it still doesn't work with mutable classes, and arguably, it really shouldn't do it with mutable, dynamic arrays either. Consider struct S { int[] arr = [1, 2, 3, 4]; } void main() { S s1; S s2; assert(s1.arr.ptr == s2.arr.ptr); assert(s1.arr == [1, 2, 3, 4]); s2.arr[3] = 5; assert(s1.arr == [1, 2, 3, 5]); } That code passes. The value of s1.arr comes from S.init.arr, so every single instance of S has an identical value for arr. With the nature of dynamic arrays, if you start appending to one, it doesn't append to the others, or if you assign the array itself a new value, it won't affect the arrays in the other stucts, but if you don't assign a new value to the array, and you mutate any of the elements, they will be mutated for all (at least so long as the array wasn't reallocated due to appending). That's almost certainly not the behavior that the programmer wants. It comes naturally from what S.init is and how an object is initialized, but it's not exactly desirable behavior. If the compiler required that direct initializations of member variables which were arrays be const, tail-const, immutable, or tail-immutable like it does with class references, then there wouldn't be a problem, but unfortunately, it doesn't. So, if you want a struct or class to have a member variable which is a mutable reference type, then you shouldn't be directly initializing it (and in most cases, can't). You should be initializing its value in the struct or class' constructor. That can get a bit annoying with a struct in that struct's don't have default constructors in D, but you'll need to either have a non-default constructor run or a factory function (or just explicitly assign it after the object is initialized) if you want the member variable to be a usable value. - Jonathan M Davis
Jan 27 2018