March 11

On Monday, 11 March 2024 at 19:22:54 UTC, Richard (Rikki) Andrew Cattermole wrote:

>

On 12/03/2024 4:31 AM, bachmeier wrote:

>

On Monday, 11 March 2024 at 08:48:47 UTC, Richard (Rikki) Andrew Cattermole wrote:

>

On 11/03/2024 9:16 PM, Alex wrote:

>

So I don't see any errors or warnings from compiler when I use uninitialized variable |a| and don't see any exception with backtrace in runtime (application is build in debug mode).

Is it expected behavior? Looks like it is not very safe approach and can lead to very unpleasant memory errors...

This is expected behavior.

The variable a was default initialized to null.

D has not got type state analysis as part of it, so it cannot detect this situation and cause an error.

It is at the top of my todo list for memory safety research for D, as the IR it requires enables other analysis and provides a framework for it to exist in.

Rather than doing that, couldn't the compiler say A a; is not valid inside @safe?

One of the improvements for type state analysis I want to make is for methods:

class Foo {
	void func(this'nonnull);
}

Instead of:

class Foo {
	void func(this'reachable);
}

That'll catch it when you try to call something.

However I'm not sure if disallowing null entering is a great idea, its going to enter through other methods so you might as well embrace catching that as well.

What I've never understood is the valid use case for A a; that would justify that line compiling. I don't do much with classes, but the same thing comes about with structs. Why would it make sense to write code like this, particularly if you've marked it @safe?

struct Foo {
  double n;
  string s = string.init;
}

Foo * f;
// Crashes
writeln(f.n);

Foo * f = void;
// Doesn't crash but gives wrong output
writeln(f.n);

Foo * g;
// Crashes
g.n = 3.3;

Foo * g = void;
g.n = 3.3;
// Correct output
writeln(g.n);
// Crashes
writeln(g.s);
March 12
On 12/03/2024 9:39 AM, bachmeier wrote:
> On Monday, 11 March 2024 at 19:22:54 UTC, Richard (Rikki) Andrew Cattermole wrote:
>> On 12/03/2024 4:31 AM, bachmeier wrote:
>>> On Monday, 11 March 2024 at 08:48:47 UTC, Richard (Rikki) Andrew Cattermole wrote:
>>>> On 11/03/2024 9:16 PM, Alex wrote:
>>>>> So I don't see any errors or warnings from compiler when I use uninitialized variable |a| and don't see any exception with backtrace in runtime (application is build in debug mode).
>>>>>
>>>>> Is it expected behavior? Looks like it is not very safe approach and can lead to very unpleasant memory errors...
>>>>
>>>> This is expected behavior.
>>>>
>>>> The variable a was default initialized to null.
>>>>
>>>> D has not got type state analysis as part of it, so it cannot detect this situation and cause an error.
>>>>
>>>> It is at the top of my todo list for memory safety research for D, as the IR it requires enables other analysis and provides a framework for it to exist in.
>>>
>>> Rather than doing that, couldn't the compiler say `A a;` is not valid inside `@safe`?
>>
>> One of the improvements for type state analysis I want to make is for methods:
>>
>> ```d
>> class Foo {
>>     void func(this'nonnull);
>> }
>> ```
>>
>> Instead of:
>>
>> ```d
>> class Foo {
>>     void func(this'reachable);
>> }
>> ```
>>
>> That'll catch it when you try to call something.
>>
>> However I'm not sure if disallowing null entering is a great idea, its going to enter through other methods so you might as well embrace catching that as well.
> 
> What I've never understood is the valid use case for `A a;` that would justify that line compiling. I don't do much with classes, but the same thing comes about with structs. Why would it make sense to write code like this, particularly if you've marked it @safe?
> 
> ```
> struct Foo {
>    double n;
>    string s = string.init;
> }
> 
> Foo * f;
> // Crashes
> writeln(f.n);
> 
> Foo * f = void;
> // Doesn't crash but gives wrong output
> writeln(f.n);
> 
> Foo * g;
> // Crashes
> g.n = 3.3;
> 
> Foo * g = void;
> g.n = 3.3;
> // Correct output
> writeln(g.n);
> // Crashes
> writeln(g.s);
> ```

Like that? No.

You may be setting the variable based upon some condition and then passing the result into a function.

Null may be a valid behavior for that function.
March 11
On 3/11/2024 3:20 AM, Alex wrote:
> Oh... looks like null is also used for refs in D. It's sad :)
> I thought it used only for pointers in unsafe mode.
> I think, the null safety feature is very important in modern world (maybe "must have" :) ). Very nice to have such feature in D like in Kotlin for example.
> So, as I understand, D team have the task in TODO list about implementation something like "null safety"?

Null is actually not a memory safety issue. What happens when null is read or written to is a seg fault. The seg fault is the hardware saying "you cannot do that", so there is nothing unsafe about it.

Memory safety is being unable to corrupt memory. The seg fault ensures no memory corruption happens.

March 11
The default value for a class reference is `null`. Most objects need an "I'm not a valid object" value, and null is ideal for it because if it is dereferenced, a hardware segment fault is generated.

If a variable is initialized with `void`, that is something completely different. The variable winds up being set to garbage, as it is not initialized at all. This is why `void` initialization for references is only allowed in code marked @safe, and is usually used when top efficiency is required.

Consider what a null class reference is good for:

```D
class ExtraInfo { ... }

struct S
{
    int a,b,c;
    ExtraInfo extra;
}
```
In my program, sometimes I need the `extra` info, but most of the time, not. Why have the compiler force an allocation for `extra` if it isn't used all the time? That just wastes time and memory.
March 11
On 3/11/2024 3:42 AM, Alex wrote:
> Yes, you right. I had hope what SafeD also support this feature in some way. But looks like not now.

Null references are not unsafe, that's why it is not in SafeD.
March 11
All a seg fault is is the hardware detecting the fault.

Consider an array overflow:

```
int test(int[] a)
{
    return a[100];
}
```
There is no way in general to detect an array overflow here, so there is a runtime check. If the check fails, a fatal array overflow exception is generated.

I'm pretty sure Rust does the same thing for array overflows.

A seg fault is no different, it's just the hardware doing the check for you, so it doesn't cost you any extra code or speed.
March 11
On 3/11/2024 12:43 PM, Steven Schveighoffer wrote:
> The explanation is that D is expecting the memory hardware to fault when you dereference null. We know that this is not the case for all situations

In particular, when a constant is added to the null reference that is large enough to skip over the protected pages in the memory space.

March 11
On 3/11/2024 1:14 PM, Alex wrote:
> Ok, got it, but on Windows I observe stange behaviour in runtime: application terminated without system error and any logs in console.

That's odd because Windows has no trouble reporting null dereferences and exiting the program.

Try running it under the VC debugger. It'll drop you right on the line of source code that dereferences the null.

March 11
On 3/11/2024 4:01 AM, Alex wrote:
> Yes, I got it about compiler, static analyzer can't detect such potential issue for now.

It cannot do it in the general case, that would be the halting problem.

> The instance of class `A` is initialized by default initializer - correct?.

`A a;` will default initialize `a` to `null`.

`A a = new A();` will allocate an instance of `A` where each field is default initialized, and assign the result to `a`.

> But what about variable `a`?

`a` is default initialized to `null`.

> Is it initialized by null or contains reference to the instance initialized by default initializer?

`null`

> What happend when I tried to call method `run()` of `a` in runtime?

`a` is passed as the `this` pointer to the method `run()`. Hence, in this case, `this` will be null. If you attempt to dereference `this`, it will seg fault.

> I see that application was abnormal termination because `writeln("Hello, world!");` was not called.
> But I don't see any information in console about it (backtrace or something else).

To get a backtrace on Windows, run it in the VC debugger.

> Is it uncatched excpetion? But I have tried to catch it - not work.

D's exception catchers do not catch Windows system exceptions for 64 bit code. Microsoft has not seen fit to document their 64 bit EH system.

March 12

On Tuesday, 12 March 2024 at 03:13:54 UTC, Walter Bright wrote:

>

On 3/11/2024 3:42 AM, Alex wrote:
Null references are not unsafe, that's why it is not in SafeD.

The Java has negative experience with null: https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/
In modern C++ preference is given to std::optional.
For developer, who want make reliable software, it menas many rutinic checks for null. But mistakes are inevitable because of human factor. On other hand compiler can do it better (with 100% guarantee).

In my opinion, Kotlin nullable types with compiler vaidation in compilation time is a powerfull feature:

A? a; // without explicit initialization is ok here, because <type>? can hold null

a.run(); // compilation error, because can be null (the type of "a" is "A?")

if (a != null) {
   a.run(); // ok, because can't be null in this branch (now type of "a" is "A")
}