August 24, 2023
On 8/24/2023 8:30 AM, Adam D Ruppe wrote:
> I wrote at greater length about this here:
> http://dpldocs.info/this-week-in-d/Blog.Posted_2022_11_14.html#redesign-for-template-emission-woes

Unfortunately, when compiling a library with one set of switches and linking it with a binary created with another set of switches, one is always going to run the risk of mismatch (and not just with -version).

For a library designed to be distributed as a binary, the interface to it needs to be carefully designed to minimize this risk.

For a library distributed as source, but intended to be built separately, it's less of an issue but it still needs to be designed to minimize the risk.

This just comes with static compilation.

A header-only library doesn't have these problems.
August 24, 2023
On Thursday, 24 August 2023 at 16:26:10 UTC, John Colvin wrote:
> We have the ability to declare information (`enum`s and so on) & branch on it at compile time (`static if`), we have a package & module system to handle who can and does read that data. Beyond that, it's the author & build system’s problem imo.

Yeah, it would be kinda interesting if `core.build_environment` just predefined some enums and `version` was eliminated.

Would be a simplification of the language and enable new things since you could maybe reflect over it! lol
August 24, 2023

On Thursday, 24 August 2023 at 17:03:20 UTC, Walter Bright wrote:

>

You wrote that this is in hundreds of places in druntime. Great! If this technique wasn't used, I guarantee that any port to AdrOS will miss more than a few, and it will be hell to figure out what is going wrong. I guarantee it because it always happened when defaults were used for unknown systems.

I understand this point, but it doesn't take out what Adam said. Sometimes you're coding for a platform where DRuntime isn't ported to, without intention to port it in whole.

I think we want to let the user to pick. By default, unimplemented DRuntime functions would cause a compilation failure, but that could be suppressed with -version=Autostub. Therefore, the versioning would be:

else version(Autostub){}
else static assert(0, "Not implemented!");

Alternatively for less verbose syntax, object.d would have

version(Autostub) enum autostubbing = true;
else enum autostubbing = false;

...making the versioning tail look like:

else static assert(autostubbing, "Not implemented!");
August 24, 2023
On Thursday, 24 August 2023 at 17:03:20 UTC, Walter Bright wrote:
> Not impossible, just initially a mite tedious. One can still incrementally port to AdrOS by adding:
>
> ```
> version (Windows)
> {
>      enum x = 7;
> }
> else version (AdrOS)
> {
>      enum x = 0; // FIXME
> }
> else
>      static assert(0);
> ```

So your solution to how static assert(0) sucks is to literally add a branch that doesn't use it.

If this isn't a full concession, I don't know what is!


But, I'd point out that this kind of thing is even worse than not having the `else static assert(0)` branch at all. Suppose you wrote it like this in the first place:

```
version(Windows)
  enum x = 8;
version(linux)
  enum x = 8;

// somewhere else

version(Windows)
  enum y = 9;
version(linux)
  enum y = 7;
```

You port it to AdrOS.

```
import that;
void main() {
   auto thing = x;
}
```

You now get a nice error: undefined identifier "x". *as you use it*, the compiler tells you it is missing and you know to go fix it. Then later, when you use y, you again get undefined identifier and you go back and add it. It is incrementally ported - always compiling the core, with more advanced test cases failing until you fill in those features.

Now, contrast to the static assert pattern:

```
version(Windows)
  enum x = 8;
else version(linux)
  enum x = 8;
else static assert(0);

// somewhere else

version(Windows)
  enum y = 6;
else version(linux)
  enum y = 7;
else static assert(0);
```

You port it to AdrOS.

```
import that;
void main() {
   auto thing = x;
}
```

You get compile errors before you even get into main! You have to do *all* the work up front to test *any* of it. So, we do the tedious thing you suggested:

```
version(Windows)
  enum x = 8;
else version(linux)
  enum x = 8;
else version(AdrOS)
  enum x = 9;
else static assert(0);

// somewhere else

version(Windows)
  enum y = 6;
else version(linux)
  enum y = 7;
else version(AdrOS)
  enum y = 0; // FIXME
else static assert(0);
```

After who knows how many hours, you've finished all that tedious nonsense and can finally compile your test program again:


```
import that;
void main() {
   auto thing = x;
}
```

Yay, hello world finally works!

Then a month later, you try to use `y`, forgetting that there's a FIXME. The program fails in mysterious ways, throwing some invalid flag exception or something, but the docs say y is a valid flag.

A few hours later, you try to `printf("%d", y)` and you see a 0. Oh yeah, you never ported that. Time to grep for FIXME again.

Complete waste of time with zero help from the compiler.

It is better to never use static assert(0) at all like this. It is good inside a template to validate arguments that the user might fix and try recompiling again.

It is not good at module level where it just wastes time.


> I guarantee it because it always happened when defaults were used for unknown systems.

I'm not suggesting using defaults for unknown systems.

> I prefer a bit of tedium to debugging hell.

I prefer neither.
August 24, 2023
On Thursday, 24 August 2023 at 17:39:34 UTC, Dukc wrote:
> I think we want to let the user to pick.

The top level static assert has no value.

I went through with this let-the-user-pick thing in simpledisplay too, like described in the blog. It was a bunch of work that led nowhere; it is better to just leave the identifier undefined and let the test process catch it.

These things look like a good idea when you're starting a new project, but they don't stand up to experience supporting things long term.

> Alternatively for less verbose syntax, `object.d` would have

hahaha another example of how D's `version` keyword is useless baggage in the language, even for this trivial thing, you'd rather have an `enum`.
August 24, 2023
On Thursday, 24 August 2023 at 16:47:55 UTC, Walter Bright wrote:
> That was something different.

No, it is a case of the same general problem. It even literally uses the `version` keyword in the language!

The best advice I have is to never use any version specifier except the built in platform ones. `version(Windows)` is not great but not demonstrably harmful the way user-defined versions or `version(unittest)` and others influenced by build flags are just because those are *so* easy to get out of sync. And `version(unittest)` is actually extraordinarily dangerous because `unittest` is so generic.

At least `version(no_fork)`, while it could be defined by multiple libraries in differing ways, is somewhat unlikely to be seen. But virtually all D code agrees on `unittest` being a condition they use since it is actually standardized.

This would be mitigated if the -unittest switch took a module pattern. -version taking a module pattern would help limit its damage. You'd still want to match when building, but at least then it would be intentionally namespaced and avoid accidental conflicts.

> I wouldn't set it up that way. I'd version on IA64 within CRuntime_DigitalMars. I.e. overlapping version declarations are not a good plan, for precisely the reason you mention. Better to version along orthogonal axes.

https://dlang.org/spec/version.html#predefined-versions

Plenty of overlapping things in that existing list.

(and i expect you meant X86_64 rather than IA64, but that falls apart if it ran on AArch64 too, so that'd be all the more reason to have a separately defined thing like Phobos did)

> It failed because none of them had ever ported code from 16 to 32 before, and so the future-proof solutions were all the wrong solutions.

Indeed. Just like with D's `version` feature, it seemed like a good idea at the time, but didn't stand up to real world use in the long run.

> At some point, you're just going to have to run:
>
>     grep -r version *

The problem with this is that `-version` is global, including across dependencies of dependencies that might not even be in tree.
August 24, 2023
On Thursday, 24 August 2023 at 17:03:20 UTC, Walter Bright wrote:
> On 8/24/2023 8:30 AM, Adam D Ruppe wrote:
>> Or when someone again tries to figure proof and puts `else static assert(0)` at the end, making incremental porting impossible. (which i also wrote about in the last year: http://dpldocs.info/this-week-in-d/Blog.Posted_2023_02_20.html#static-assert-patterns-arguably-harmful-for-porting )
>
> Not impossible, just initially a mite tedious. One can still incrementally port to AdrOS by adding:
>
> ```
> version (Windows)
> {
>      enum x = 7;
> }
> else version (AdrOS)
> {
>      enum x = 0; // FIXME
> }
> else
>      static assert(0);
> ```
>

For import or declaration, I refer to use
version (Windows)
{
    enum x = 7;
}
else
    pragma(msg, "Unsupport system for " ~ __MODULE__);

For function logic, still refer to use static assert
int foo()
{
    version (Windows)
    {
        return 7;
    }
    else
        static assert(0, "Unsupport system for " ~ __FUNCTION__);
}

August 25, 2023
There is an alternative solution which would be much simpler.

If the scope for which a static assert is located, does not contribute towards symbols that get used and another is found, it won't fire.

I.e.

```d
void func() {
	static assert(false);
}
```

As long as you don't call func, it won't fire.

Same goes for:

```d
module binding.b;

version(Windows) {
	extern export void func();

} else {
	static assert(0, "Unimplemented");
}

module binding.a;

void func() {
}
```

There would need to be a way to be stricter and go back to the way we have it now via cli. But as far as making porting easier? Yeah it can't be beat.
August 24, 2023
On Thursday, 24 August 2023 at 20:29:38 UTC, An Pham wrote:
> For function logic, still refer to use static assert
> int foo()

This is still going to stop compilation so it doesn't scale very well. I've taken to `throw new NotYetImplementedException();` instead since then you can build.
August 24, 2023
On Thursday, 24 August 2023 at 20:51:26 UTC, Richard (Rikki) Andrew Cattermole wrote:
> If the scope for which a static assert is located, does not contribute towards symbols that get used and another is found, it won't fire.

You can kinda achieve this with a zero arg template today:

void func()() {
     static assert(0); // only triggered if actually called
}

I use this for a lot of things including optional dependencies too.

> There would need to be a way to be stricter and go back to the way we have it now via cli.

`dmd -i main.d` would only trigger static assert failures in modules actually imported, so that can work for a while too, then `dmd *.d` to build all.

But none of these offer compelling advantages over `grep -r "NotYetImplemented"`