6 days ago
On Thursday, 17 April 2025 at 05:09:41 UTC, Walter Bright wrote:
> ```C
> int foo(int * arg1, int * NULL_UNSPECIFIED arg2, int * NULLABLE arg3) {
> ```
>
> https://www.youtube.com/watch?v=vEr0EAcSWcc

Yeah, but that is test code as I'm experimenting with the mechanism, and trying to figure out how it can be used, and what (if any) value I perceive it as adding.

I rather suspect the norm would to use the pragma such that most uses are implicitly non-null, and only the exceptional nullable pointer would have to be so marked.

On this particular point, my preferred form would be something similar to what Cyclone had, but not using its syntax forms.  Possibly using the syntax from Zig.

So the common case would be a non-nuull pointer, using '*' alone in its declaration.  The nullable point would probably be declared as '?*' (or '*?' - not sure which).

Then for use, one could not assign from a nullable to a non-null unless DFA had already proved the value was not null.

Except those would be non backward compatible changes; so one would somehow have to opt in to them.
6 days ago
On Thursday, 17 April 2025 at 05:31:48 UTC, Walter Bright wrote:
> [...]
> I don't see how a runtime detector can work better than a seg fault with stack trace.

Actually I have implemented a runtime detector in another language and that gives me
 -- quite rarely -- things like

> temp.sx:11:7: runtime error, member read with null `this`

So that you know directly where the problem is and often there's even no need for running again in gdb. That is implemented in the Dlang equivalent of the DotVarExp. Concretly it's about codegening the same as for an assertion and just right before loading from the LHS.

That's why, previously in the thread, I called that an "implicit contract". Aren't assertions the most primitive form of contracts ?
6 days ago
Here's something I spent 5 minutes on:

```d
struct NonNull(T)
{
    T* p;
    T* ptr() { return p; }
    alias this = ptr;
}

int test(NonNull!int np)
{
    int i = *np;
    int* p1 = np;
    int* p2 = np.ptr;
    np = p1; // Error: cannot implicitly convert expression `p1` of type `int*` to `NonNull!int`
    return i;
}
```
Note that NonNull can be used as a pointer, can be implicitly converted to a pointer, but a pointer cannot be implicitly converted to a NonNull.

There's more window dressing one would want, like a constructor with a null check, but the basic idea looks workable and is not complicated.
6 days ago

On Thursday, 17 April 2025 at 16:39:28 UTC, Walter Bright wrote:

>

Here's something I spent 5 minutes on:

struct NonNull(T)
{
    T* p;
    T* ptr() { return p; }
    alias this = ptr;
}

int test(NonNull!int np)
{
    int i = *np;
    int* p1 = np;
    int* p2 = np.ptr;
    np = p1; // Error: cannot implicitly convert expression `p1` of type `int*` to `NonNull!int`
    return i;
}

Note that NonNull can be used as a pointer, can be implicitly converted to a pointer, but a pointer cannot be implicitly converted to a NonNull.

There's more window dressing one would want, like a constructor with a null check, but the basic idea looks workable and is not complicated.

First to support classes, you’d have to make a slight change

struct NonNull(T)
{
    T p;
    T ptr() { return p; }
    alias this = ptr;
}

It works, but the syntax/defaults is backwards. Why does the unusual case of a nullable pointer get the nice syntax while the common case gets the NonNull!(int*) syntax? Who is going to write that all over their code?

6 days ago
On Thursday, April 17, 2025 11:36:49 AM MDT Dave P. via Digitalmars-d wrote:
> On Thursday, 17 April 2025 at 16:39:28 UTC, Walter Bright wrote:
> First to support classes, you’d have to make a slight change
> ```d
> struct NonNull(T)
> {
>      T p;
>      T ptr() { return p; }
>      alias this = ptr;
> }
> ```
>
> It works, but the syntax/defaults is backwards. Why does the unusual case of a nullable pointer get the nice syntax while the common case gets the `NonNull!(int*)` syntax? Who is going to write that all over their code?

I am in favor of making changes along the lines that Rikki is proposing so that we can better handle failure in multi-threaded environments without having to kill the entire program (and in addition, there are cases where dereferencing null pointers is not actually memory-safe, because either that platform doesn't protect it or a compiler like ldc or gdc will optimize the code based on the fact that it treats null pointer dereferencing as undefined behavior).

That being said, I honestly think that the concern over null pointers is completely overblown. I can't even remember the last time that I encountered one being dereferenced. And when I have, it's usually because I used a class and forgot to initialize it, which blows up very quickly in testing rather than it being a random bug that occurs during execution. So, if someone feels the need to use non-null pointers of some kind all over the place, I'd be concerned about how they're writing their code such that it's even a problem - though it could easily be the case that they're just paranoid about it, which happens with sometimes with a variety of issues and people. And as such, I really don't think that it's all that big a deal if a wrapper type is required to guarantee that a pointer isn't null. Most code shouldn't need anything of the sort.

However, as I understand it, there _are_ memory-safety issues with null pointers with ldc (and probably gdc as well) due to how they optimize. IIRC, the core problem is that they treat dereferencing a null pointer as undefined behavior, and that can have serious consequences with regards to what the code does when there actually is a null pointer. dmd doesn't have the same problem from what I understand simply because it's not as aggressive with its optimizations. So, Walter's stance makes sense based on what dmd is doing, but it doesn't necessarily make sense with D compilers in general.

So, between that and the issues with platforms such as webasm, I am inclined to think that we should treat dereferencing pointers like we treat accessing elements of arrays and insert checks that the compiler can then optimize out. And we can provide flags to change that behavior just like we do with array bounds checking. But if we cannot guarantee that attempting to dereference a null pointer is always @safe (and as I understand it, outside of using dmd, we can't), then that's a hole in @safe. And no matter how rare dereferencing null is or isn't in practice, we can't have holes in @safe and have it actually give guarantees about memory safety.

- Jonathan M Davis




6 days ago
On 4/17/2025 10:36 AM, Dave P. wrote:
> First to support classes, you’d have to make a slight change

Sure, you'd want to overload the NonNull template with one that takes a class type parameter.


> It works, but the syntax/defaults is backwards. Why does the unusual case of a nullable pointer get the nice syntax while the common case gets the `NonNull!(int*)` syntax? Who is going to write that all over their code?

Backwards compatibility. The NonNull is the addition, the nullable is the existing. Changing the existing behavior would be a massive disruption.

Anyhow, it's a good idea to see how far one can take the metaprogramming approach to what you want, before changing the core language.

6 days ago
I'd like to know what those gdc and ldc transformations are, and whether they are controllable with a switch to their optimizers.

I know there's a problem with WASM not faulting on a null dereference, but in another post I suggested a way to deal with it.
6 days ago
On 18/04/2025 2:35 PM, Walter Bright wrote:
>     It works, but the syntax/defaults is backwards. Why does the unusual
>     case of a nullable pointer get the nice syntax while the common case
>     gets the |NonNull!(int*)| syntax? Who is going to write that all
>     over their code?
> 
> Backwards compatibility. The NonNull is the addition, the nullable is the existing. Changing the existing behavior would be a massive disruption.

D is designed around the type state initialized, aka nullable.

This was a _very_ smart thing to do, before type state analysis was ever mainstream. Walter this is by far one of the best design decisions you have ever made.

Trying to change the default type state for pointers to non-null would be absolutely horrific.

Its possible to prove a variable is non-null, but if we start painting pointers themselves in the type system? Ughhhhhh, the pain application VM languages are having over this isn't worth it, in the context of D. We can do a lot better than that.

If this doesn't work without annotation (in a single compilation run), we've failed.

```d
void main() {
	func(new int); // ok
	func(null); // error
}

void func(int* ptr) {
	int v = *ptr;
}
```

6 days ago
On Thursday, 17 April 2025 at 22:12:22 UTC, Jonathan M Davis wrote:
> On Thursday, April 17, 2025 11:36:49 AM MDT Dave P. via Digitalmars-d wrote:
>> On Thursday, 17 April 2025 at 16:39:28 UTC, Walter Bright wrote:
>> First to support classes, you’d have to make a slight change
>> ```d
>> struct NonNull(T)
>> {
>>      T p;
>>      T ptr() { return p; }
>>      alias this = ptr;
>> }
>> ```
>>
>> It works, but the syntax/defaults is backwards. Why does the unusual case of a nullable pointer get the nice syntax while the common case gets the `NonNull!(int*)` syntax? Who is going to write that all over their code?
>
> That being said, I honestly think that the concern over null pointers is completely overblown. I can't even remember the last time that I encountered one being dereferenced. And when I have, it's usually because I used a class and forgot to initialize it, which blows up very quickly in testing rather than it being a random bug that occurs during execution.

When you work on a team with less skilled/meticulous teammates on high performance, highly parallel software, you realize how amazing it would be to have a language where pointers are guaranteed to be non-null by default. I spent so much time fixing random annoying NPEs that other people wrote that pop up randomly in our test cluster.

This feature is not for people like you. It is to protect your sanity from the terrible code that people who are not as principled will inevitably write. This is an absolute no-brainer IMO, and it's one thing that is really great about Rust.
5 days ago

On Thursday, 17 April 2025 at 16:39:28 UTC, Walter Bright wrote:

>

Here's something I spent 5 minutes on:

struct NonNull(T)
{
    T* p;
    T* ptr() { return p; }
    alias this = ptr;
}

int test(NonNull!int np)
{
    int i = *np;
    int* p1 = np;
    int* p2 = np.ptr;
    np = p1; // Error: cannot implicitly convert expression `p1` of type `int*` to `NonNull!int`
    return i;
}

Note that NonNull can be used as a pointer, can be implicitly converted to a pointer, but a pointer cannot be implicitly converted to a NonNull.

There's more window dressing one would want, like a constructor with a null check, but the basic idea looks workable and is not complicated.

Isn’t ref essentially a non-null pointer?