January 14, 2022
On Fri, Jan 14, 2022 at 07:24:22PM +0100, Timon Gehr via Digitalmars-d wrote:
> On 1/14/22 18:41, H. S. Teoh wrote:
> > But in D, whenever a variable is declared, it gets initialized to its default value unless specified otherwise. Since nothing is specified here, it ought to perform its default initialization, and since there is no value with which it can be initialized, it ought to raise a runtime exception.
> 
> I don't follow why this is necessarily desirable. E.g., if generic code checks whether or not a type can be default constructed, it will by default get a spurious "yes" answer and has to special case `noreturn`.

? Isn't that exactly what it should be?  If generic code checks whether type T can be default constructed, and T happens to be noreturn, then it would fail because noreturn.init is not constructible.  Presumably, the code is doing this check because it's about to create a variable of type T afterwards, so returning "yes" would lead to instantiating noreturn variables. Why would that be a good thing?


T

-- 
The best way to destroy a cause is to defend it poorly.
January 14, 2022

On 1/14/22 6:52 AM, WebFreak001 wrote:

>

For example:

import std.stdio;

void foo(noreturn a)
{
     writeln("we got called!");
}

void bar()
{
     noreturn b;
     writeln("calling");
     foo(b);
}

void main()
{
     writeln("bar");
     bar();
     writeln("done");
}

Guess what it prints and reply to it on this thread. I think the result is pretty nonsensical.

Reveal: https://run.dlang.io/is/4SXQal

Should this usage be allowed? I would say not allowing it would make more sense.

Additionally if we don't allow it we could allow aliasing to it like:

deprecated("OldType is no longer supported, use NewType instead") alias OldType = noreturn;

and with that both add hint messages for users to migrate and not allow broken code to compile.

A sample for this would be libdparse, where the arguments of the visit functions determine which function is called, even if the argument isn't actually used or just ignored. The cases where the argument type is specified, but not used, do happen and migrating types there to force the user to rewrite code is currently not possible without completely removing the type.

I have several thoughts here:

  1. I can't find noreturn in the spec, so I'm not sure what exactly should happen here. In the library it has very very sparse documentation. This needs correction. At a minimum, it should go into the Types page.

  2. The DIP clearly states that:

    Defining a variable with type noreturn with an initialization expression will simply generate that expression as soon as it becomes live. Defining a noreturn variable with no initialization expression generates an assert(0) only if the variable is accessed, which can be useful in generic code where unused noreturn variables may be declared. Generating the assert(0) as soon as the variable is live in these cases could prematurely end the program.

So if the DIP is implemented as defined, the code in question is not assigning an initial value, and if it did, it should generate an assert. BUT, it's clearly being used, which means the call to foo(b) should be the equivalent of foo(assert(0)).

  1. I wonder if there's some kind of dead-store optimization happening to thwart the error?

-Steve

January 14, 2022

On Friday, 14 January 2022 at 18:49:26 UTC, Steven Schveighoffer wrote:

>
  1. I can't find noreturn in the spec, so I'm not sure what exactly should happen here. In the library it has very very sparse documentation. This needs correction. At a minimum, it should go into the Types page.

I made a PR to document noreturn with an example of a function that returns noreturn. I haven't found a good example for other behaviour yet. Some of the cases in the DIP don't work yet in stable dmd (explained in a comment):
https://github.com/dlang/dlang.org/pull/3149
https://github.com/dlang/dlang.org/pull/3149#issuecomment-1001009778

I also made a PR for main() returning noreturn or inferred return.

January 14, 2022
On 1/14/22 19:32, H. S. Teoh wrote:
> On Fri, Jan 14, 2022 at 07:24:22PM +0100, Timon Gehr via Digitalmars-d wrote:
>> On 1/14/22 18:41, H. S. Teoh wrote:
>>> But in D, whenever a variable is declared, it gets initialized to
>>> its default value unless specified otherwise. Since nothing is
>>> specified here, it ought to perform its default initialization, and
>>> since there is no value with which it can be initialized, it ought
>>> to raise a runtime exception.
>>
>> I don't follow why this is necessarily desirable. E.g., if generic
>> code checks whether or not a type can be default constructed, it will
>> by default get a spurious "yes" answer and has to special case
>> `noreturn`.
> 
> ? Isn't that exactly what it should be?  If generic code checks whether
> type T can be default constructed, and T happens to be noreturn, then it
> would fail because noreturn.init is not constructible.

noreturn.init is assert(0), which compiles.

> Presumably, the
> code is doing this check because it's about to create a variable of type
> T afterwards, so returning "yes" would lead to instantiating noreturn
> variables. Why would that be a good thing?
> 
> 
> T
> 

My point was the opposite of what you now seem to be taking it to be. You said there ought to be default initialization, which for noreturn is a runtime exception. I asked why this "ought" to be so.
January 14, 2022

On Friday, 14 January 2022 at 16:32:22 UTC, Paul Backus wrote:

>

That's not what the bug is here. noreturn is the type of expressions whose evaluation does not halt; i.e., it is a type with no values. Therefore, declaring a variable of type noreturn is a no-op: all it does is add a symbol to the current scope. No initialization is performed, because there is nothing to initialize.

Then, should "noreturn x = noreturn.init;" fail? In my world, it is equivalent to "noreturn x;". There is still an implicit initialization, which should halt.

January 15, 2022

On Friday, 14 January 2022 at 22:55:37 UTC, Max Samukha wrote:

>

On Friday, 14 January 2022 at 16:32:22 UTC, Paul Backus wrote:

>

That's not what the bug is here. noreturn is the type of expressions whose evaluation does not halt; i.e., it is a type with no values. Therefore, declaring a variable of type noreturn is a no-op: all it does is add a symbol to the current scope. No initialization is performed, because there is nothing to initialize.

Then, should "noreturn x = noreturn.init;" fail? In my world, it is equivalent to "noreturn x;". There is still an implicit initialization, which should halt.

My point is, they're not equivalent.

  • Execution of noreturn x = noreturn.init; requires evaluating the expression noreturn.init.
  • Execution of noreturn x; does not require evaluating anything.

Yes, this is an exception to the general rule that T x = T.init and T x; are equivalent.

Why does this exception exist? Because for any type T other than noreturn, the expression T.init evaluates to a value of type T. However, the expression noreturn.init does not evaluate to a value of type noreturn--because, as previously stated, there is no such thing as a value of type noreturn.

January 15, 2022

On Saturday, 15 January 2022 at 00:54:03 UTC, Paul Backus wrote:

>

Why does this exception exist? Because for any type T other than noreturn, the expression T.init evaluates to a value of type T. However, the expression noreturn.init does not evaluate to a value of type noreturn--because, as previously stated, there is no such thing as a value of type noreturn.

I understand the argument and counterarguments. I don't know what is the right way. I err on the side of not introducing the special case: every variable in D requires initialization, and initialization of noreturn should fail because the initializer halts. You argue that noreturn doesn't have a value, so we don't even need to evaluate the initializer. Tight corner.

January 15, 2022

On Friday, 14 January 2022 at 20:59:18 UTC, Timon Gehr wrote:

>

On 1/14/22 19:32, H. S. Teoh wrote:

>

On Fri, Jan 14, 2022 at 07:24:22PM +0100, Timon Gehr via Digitalmars-d wrote:

>

On 1/14/22 18:41, H. S. Teoh wrote:

>

But in D, whenever a variable is declared, it gets initialized to
its default value unless specified otherwise. Since nothing is
specified here, it ought to perform its default initialization, and
since there is no value with which it can be initialized, it ought
to raise a runtime exception.

I don't follow why this is necessarily desirable. E.g., if generic
code checks whether or not a type can be default constructed, it will
by default get a spurious "yes" answer and has to special case
noreturn.

? Isn't that exactly what it should be? If generic code checks whether
type T can be default constructed, and T happens to be noreturn, then it
would fail because noreturn.init is not constructible.

noreturn.init is assert(0), which compiles.

>

Presumably, the
code is doing this check because it's about to create a variable of type
T afterwards, so returning "yes" would lead to instantiating noreturn
variables. Why would that be a good thing?

T

My point was the opposite of what you now seem to be taking it to be. You said there ought to be default initialization, which for noreturn is a runtime exception. I asked why this "ought" to be so.

Not to put words in HS's mouth, but I think the reason why simply declaring the noreturn variable should lead to initialisation is because that's how all variable declarations in D behave, unless you disable default initialisation, at which point it's a compiler error to simply declare the variable.

noreturn not default initializing is therefore a special case, which I feel is undesirable

January 15, 2022
On 15.01.22 12:02, Tejas wrote:
>>
> 
> Not to put words in HS's mouth, but I think the reason why simply declaring the `noreturn` variable should lead to initialisation is because that's how _all_ variable declarations in D behave, unless you disable default initialisation, at which point it's a compiler error to simply declare the variable.
> ...

Exactly. Unless.

> `noreturn` not default initializing is therefore a special case, which I feel is undesirable

Maybe, but why is it more desirable to pretend that the empty type can be default-initialized? You can still declare noreturn variables with an explicit initializer, but it seems a bit shady to make `assert(0)` the default of anything.
January 15, 2022

On Saturday, 15 January 2022 at 09:52:00 UTC, Max Samukha wrote:

>

On Saturday, 15 January 2022 at 00:54:03 UTC, Paul Backus wrote:

>

Why does this exception exist? Because for any type T other than noreturn, the expression T.init evaluates to a value of type T. However, the expression noreturn.init does not evaluate to a value of type noreturn--because, as previously stated, there is no such thing as a value of type noreturn.

I understand the argument and counterarguments. I don't know what is the right way. I err on the side of not introducing the special case: every variable in D requires initialization, and initialization of noreturn should fail because the initializer halts. You argue that noreturn doesn't have a value, so we don't even need to evaluate the initializer. Tight corner.

Making initialization of noreturn fail at runtime would require two special cases: first, the special case of noreturn having no values when every other type in D has at least one value; and second, the special case of noreturn's default initializer not evaluating to a noreturn value when every other type's default initializer evaluates to a value of that type.

There is an argument to be made that we should never have accepted special case #1 in the first place (i.e., that the noreturn DIP should have been rejected). But given that we have, I think it is better to accept the consequences that follow from that decision, strange though they may seem, than to introduce even more special cases to try and paper over them.