March 14

On Wednesday, 13 March 2024 at 07:43:23 UTC, Alex wrote:

>

I mean nullable types like in Kotlin (originally in Ceylon) and null safety model around it: https://kotlinlang.org/docs/null-safety.html

if (a != null) {
    a.run(); // ok, because type of variable 'a' is A (not nullable) in this branch.
}

I like this, can the compiler handle cases like this?

if (a == null)
	return;
a.run();
March 14

On Wednesday, 13 March 2024 at 20:34:42 UTC, Alex wrote:

>

On Wednesday, 13 March 2024 at 19:58:24 UTC, Steven Schveighoffer wrote:

>

Having a possibly-null pointer is no different. D defines that in safe code, a pointer will be valid or null. The "check" occurs on use, and is performed by the hardware.

But the fundamental difference that null pointer checks is performed in D at runtime but in Kotlin (and other languages which support null safety) at compilation time.

No, Kotlin also performs a check at runtime. The same hardware runs the machine code, and the MMU is the same one that runs D code.

However, the check is not considered to cost anything. It's part of how the MMU works.

-Steve

March 14

On Wednesday, 13 March 2024 at 21:57:17 UTC, Jonathan M Davis wrote:

>

On Wednesday, March 13, 2024 1:43:23 AM MDT Alex via Digitalmars-d wrote:
What shared is supposed to do is give an error if you attempt do anything with a shared variable that isn't guaranteed to be thread-safe - which basically means that it should give an error when you actually try to do much of anything with a shared variable. However, it's not fully implemented by default right now (it will currently give an error in some cases but not all). The -preview=nosharedaccess switch can be used to make accessing shared variables an error in general (like it's supposed to be), but it hasn't been enabled by default yet.

Thank you for detailed explanation!

March 14

On Thursday, 14 March 2024 at 00:32:47 UTC, cc wrote:

>

On Wednesday, 13 March 2024 at 07:43:23 UTC, Alex wrote:

>

I mean nullable types like in Kotlin (originally in Ceylon) and null safety model around it: https://kotlinlang.org/docs/null-safety.html

if (a != null) {
    a.run(); // ok, because type of variable 'a' is A (not nullable) in this branch.
}

I like this, can the compiler handle cases like this?

if (a == null)
	return;
a.run();

Yes, sure, Kotlin compiler handle it. The analyzer controls variable type in each branch. After if type of a will be non-nullable type A.

March 14

On Thursday, 14 March 2024 at 02:09:15 UTC, Steven Schveighoffer wrote:

>

No, Kotlin also performs a check at runtime. The same hardware runs the machine code, and the MMU is the same one that runs D code.

However, the check is not considered to cost anything. It's part of how the MMU works.

Yes, sure, you right. Checks will be in runtime too and thanks to MMU hardware checks are cheap.

To be clear:

  • Kotlin compiler rejects code wthout all required null reference checks for nullable type.
  • Null refrence checks will be performed in runtime (like in D).
  • Kotlin compiler give 100% guarantee that null pointer exception never happend in runtime (exclude unsafe operations where developer take responsibility).

I give Kotlin as example because familar with it, but other languages with null safety do something like this.
And this powerful feature is possible thanks to language type system. The nullable type is special case of cool idea - the union type:
https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html
http://web.mit.edu/ceylon_v1.3.3/ceylon-1.3.3/doc/en/spec/html_single/#unionandintersectiontypes

March 14
On Wednesday, 13 March 2024 at 06:05:35 UTC, Walter Bright wrote:
> In general, we cannot guarantee that assert()'s won't trip at runtime, nor buffer overflow exceptions. All we can do is say we stop the program when it happens.

An assert is explicit in the source code, it's visible to the programmer. A null dereference can easily happen without anything in the code to suggest to the programmer that the program might abort. This is because quite often a pointer/reference is never null in a particular scope (e.g. function parameters), so programmers stop worrying about null, and sometimes do this when the pointer actually can be null. What is needed is:

1. A way of distinguishing between never null pointers and sometimes null pointers.
2. Have a syntactical opt-in way of force unwrapping a nullable pointer. This can even be a no-op so you still get the hardware checking efficiency.

> Actual memory corruption is infinitely worse than promptly stopping the program when it detects an internal bug.

Absolutely, though most modern languages seem to have some support for null safety too.

> Consider the following:
> ```
> class A { void bar(); }
>
> void foo(int i) {
>     A a;
>     if (i) a = new A();
>     ...
>     if (i) a.bar();
> }
> ```
> What happens if we apply data flow analysis to determine the state of `a` when it calls `bar()`? It will determine that `a` has the possible values (`null`, new A()`). Hence, it will give an error that `a` is possibly null at that point.

I think you don't need DFA at least as I understand it:

>    if (i) a = new A();

If `a` is non-nullable, this line could be made to error because there is no `else` branch that also initializes `a`. This is what cppfront does. non-nullable types need initialization.

>    if (i) a.bar();

If `a` is nullable, this line is an error because you are calling a method that needs an A when (from the DFA-less compiler's point of view) you might only have a null pointer.

To handle that, you either:
* `assert(a)` before `a.bar()`, and the compiler assumes `a` is not null and in release mode there is only a hardware null check.
* Call a function to force-unwrap the nullable type to a non-null type, e.g. `a.unwrap.bar()`. This function can be a no-op in terms of hardware, but requiring calling it makes the programmer aware that a possible null dereference (at least from the compiler POV) may occur.
* Rewrite the if statement to `if (a)`. That is actually better code because you would need to check that `i` hadn't changed in between the two if statements, which might be long, to understand the code.

I hope you agree that at least some of this is workable and beneficial.
Now, as regards D, I'm not sure the best way to add non-nullable types to the language. Even editions may not be enough, perhaps a compiler switch to do null checking.

Non-nullable types are also very useful for API documentation. It's common for docs to forget to say "don't pass null", the user has to check the source code if available. The API can't be misused when the type system actually carries the information about whether the pointer actually exists or not (info that should be absolutely fundamental to a good static type system).
March 16

On Wednesday, 13 March 2024 at 19:58:24 UTC, Steven Schveighoffer wrote:

>

On Wednesday, 13 March 2024 at 19:36:01 UTC, Alex wrote:

>

On Wednesday, 13 March 2024 at 18:20:12 UTC, Steven Schveighoffer wrote:

>
  • The path D takes is, let the hardware solve it.
  • The path Java takes is to instrument dereferences on possibly-null variables and throw an exception if it's null.
  • The path many other languages take is to force you to validate it's not null before using it.

Rust doesn't allow null references at all (exclude unsafe code). It is one more alternative path.

Then "optional" types, basically the same thing. Null is a memory safe "invalid thing".

I haven't read this whole thread, but I've seen responses to the above, and I'd like to add my own comment. The difference between null and optional types in Rust is that the Rust compiler forces you to handle an optional type in order to obtain its value. You can't get at the value without processing the optional with a 'match' (or related constructs, such as 'if let'), which requires handling both possibilities -- Some and None. The compiler therefore forces you to handle the unusual case, so if it happens, the result will be something under your control.

Null and other unexpected values are simply not equivalent. You are not forced to provide code to handle the possibility of the unexpected value. When your code blindly forges ahead, you may get a seg-fault. Depending on how the code was compiled, you may or may not be headed for a session with gdb.

Related to the above, you may also process an uninitialized value, at which point anything can happen. To me, this is one of the weak spots in D, inherited from C. It can lead to an incorrect result returned without error. D's use of default initializers (as opposed to C's random garbage) does not fully protect against this. Rust does prevent this.

March 18

On Wednesday, 13 March 2024 at 19:58:24 UTC, Steven Schveighoffer wrote:

>

Building non-null into the type indeed means as long as you have that type, you don't have to check. But to get it into that type, if you started with a possibly-invalid value, somewhere you had to do a check.
...
Having a possibly-null pointer is no different. D defines that in safe code, a pointer will be valid or null. The "check" occurs on use, and is performed by the hardware.

One important difference is that D makes the null check as late as possible. Often in code using non-nullable types, the check gets done earlier, nearer to where the problem is. E.g. when a function produces a pointer, but the pointer doesn't actually get dereferenced there but is stored and then some time later it gets dereferenced. Then it's not easy to find where the null pointer was actually produced. Having non-null pointers can save time debugging.

March 18

On Saturday, 16 March 2024 at 19:10:56 UTC, Don Allen wrote:

>

The compiler therefore forces you to handle the unusual case, so if it happens, the result will be something under your control.

You can call unwrap on the Option which will panic if it's None. But that's fine, because that call makes it clear to anyone reading the code that the programmer is intentionally assuming the Option contains a value.

...

>

Related to the above, you may also process an uninitialized value, at which point anything can happen.

It can't violate memory safety:

>

Void initializers for variables with a type that may contain unsafe values (such as types with pointers) are not allowed in @safe code.

https://dlang.org/spec/declaration.html#void_init

March 18
On 3/18/24 13:31, Nick Treleaven wrote:
> 
> ...
>> Related to the above, you may also process an uninitialized value, at which point anything can happen.
> 
> It can't violate memory safety:

```d
import std.stdio;
void main(){
    bool b = void;
    if(b) writeln("yes");
    if(!b) writeln("no");
}
```