4 days ago
On 4/18/2025 12:21 AM, Ogion wrote:
> Isn’t `ref` essentially a non-null pointer?

It's supposed to be. But you can write:
```d
int* p = null;
ref r = *p;
```
and you get a null ref.
4 days ago
On 4/17/2025 7:48 PM, Richard (Rikki) Andrew Cattermole wrote:
> ```d
> void main() {
>      func(new int); // ok
>      func(null); // error
> }
> 
> void func(int* ptr) {
>      int v = *ptr;
> }
> ```

It always looks simple in such examples, but then there are things like:

```d
struct S { int a,b; int* p; }

void main()
{
    S s;
    funky(&s);
    func(s.p);
}
```
where trying to track information in structs gets complicated fast.

4 days ago
On 4/17/2025 12:21 AM, Dave P. wrote:
> It’s funny though, when I first started using this kind of thing it caught loads of bugs. But after years of use, I realized it hadn’t caught any bugs in a long time. I had internalized the rules.

Same thing happened to me. When I pointed it out on HackerNews recently, I was lambasted for being "arrogant". LOL.

I make different kinds of mistakes these days. Mostly due to failure to understand the problem I am trying to solve, rather than coding errors.
4 days ago
On 4/17/2025 4:03 AM, user1234 wrote:
> Aren't assertions the most primitive form of contracts ?

Yes, indeed they are.
4 days ago
On 19/04/2025 6:11 PM, Walter Bright wrote:
> On 4/17/2025 7:48 PM, Richard (Rikki) Andrew Cattermole wrote:
>> ```d
>> void main() {
>>      func(new int); // ok
>>      func(null); // error
>> }
>>
>> void func(int* ptr) {
>>      int v = *ptr;
>> }
>> ```
> 
> It always looks simple in such examples, but then there are things like:
> 
> ```d
> struct S { int a,b; int* p; }
> 
> void main()
> {
>      S s;
>      funky(&s);
>      func(s.p);
> }
> ```
> where trying to track information in structs gets complicated fast.

Yeah indirection.

It is not a solved problem, even if you throw the type system with say a type qualifier into the mix.

https://kotlinlang.org/docs/java-to-kotlin-nullability-guide.html

"Tracking multiple levels of annotations for pointers pointing to pointers would make the checker more complicated, because this way a vector of nullability qualifiers would be needed to be tracked for each symbol. This is not a big caveat, since once the top level pointer is dereferenced, the symvol for the inner pointer will have the nullability information. The lack of multi level annotation tracking only observable, when multiple levels of pointers are passed to a function which has a parameter with multiple levels of annotations. So for now the checker support the top level nullability qualifiers only.:

int * __nonnull * __nullable p;
int ** q = p;
takesStarNullableStarNullable(q);"

https://clang.llvm.org/docs/analyzer/developer-docs/nullability.html

Its actually a pretty good example of why I think supporting modelling of fields is not worth our time. Plus throw in multi-threading and boom shuckala, not modellable anymore.

4 days ago
On Saturday, 19 April 2025 at 06:05:53 UTC, Walter Bright wrote:
> On 4/18/2025 12:21 AM, Ogion wrote:
>> Isn’t `ref` essentially a non-null pointer?
>
> It's supposed to be. But you can write:
> ```d
> int* p = null;
> ref r = *p;
> ```
> and you get a null ref.

How is this possible? Shouldn't dereferencing p crash the program before r is initialized?
4 days ago
On Saturday, April 19, 2025 3:17:29 AM MDT Meta via Digitalmars-d wrote:
> On Saturday, 19 April 2025 at 06:05:53 UTC, Walter Bright wrote:
> > On 4/18/2025 12:21 AM, Ogion wrote:
> >> Isn’t `ref` essentially a non-null pointer?
> >
> > It's supposed to be. But you can write:
> > ```d
> > int* p = null;
> > ref r = *p;
> > ```
> > and you get a null ref.
>
> How is this possible? Shouldn't dereferencing p crash the program before r is initialized?

It doesn't, because nothing is actually dereferenced. This is like if you have a null pointer to a struct and then call a member variable on it, e.g.

```
void main()
{
    S* s;
    s.foo();
}

struct S
{
    int i;

    void foo()
    {
    }
}
```

This does not crash, because s is never actually used. It's just passed to foo. Of course, if you then changed foo to something like

```
void foo()
{
    import std.stdio;
    writeln(i);
}
```

it would crash, because then it would need to dereference s to access its i member, but until it needs to access a member, there's no reason for any dereferencing to take place.

The same happens with C++ classes as long as the function isn't virtual.

Where you _do_ get it failing in basically any language would be with a class' virtual function, because the class reference needs to be dereferenced in order to get the correct function.

This is one of those bugs that can be _very_ confusing if you don't think it through, since you do naturally tend to assume that when you call a member function, the pointer is dereferenced, but if the function isn't virtual, there's no reason to dereference it to make the call. The funciton is just passed the pointer or reference as in invisible argument. So, you can end up with a segfault inside of your function instead of at the call site and get rather confused by it. It's happened to me a couple of times in my career, and it's initially been pretty confusing each time, even though after it was explained to me the first time, I understood it, because you just tend to think of calling a member function as derferencing the object even though it it doesn't actually have any reason to do so unless the function is virtual.

And with

int* p = null;
ref r = *p;

no dereferencing occurs, because the compiler is converting int* to ref int, and underneath the hood, ref int is just int*, so it's simply copying the value of the pointer.

- Jonathan M Davis




4 days ago

On Friday, 18 April 2025 at 07:21:43 UTC, Ogion wrote:

>

Isn’t ref essentially a non-null pointer?

Not really, it is similar but not the same.

The thing pointers give, or rather the syntax of initialising them gives, is easy local reasoning, due to having to explicitly use the address of (&) operator.

So any function in a different compilation unit is a lot easier to reason about if one does not have to worry about if an argument may experience an under the covers change due to the parameter being defined as a reference.

Sadly, that state already exists, so it is rather a case of water under the bridge.

4 days ago
On Saturday, 19 April 2025 at 10:35:01 UTC, Jonathan M Davis wrote:
> [...] because then it would need to dereference s to access its i member, but until it needs to access a member, there's no reason for any dereferencing to take place.
>
> The same happens with C++ classes as long as the function isn't virtual.

That is undefined behavior. In the C++ standard null references have been carefully ruled out [1]. There is no standard conforming C++ program having null references.

> And with
>
> int* p = null;
> ref r = *p;
>
> no dereferencing occurs,

In C++ this is a programming error. When creating a reference from
a pointer the null check it is necessary in order to uphold C++' guarantee
that references are actually bound to existing objects.

[1] google.com?q="c++ reference from null pointer"
    - https://old.reddit.com/r/cpp/comments/80zm83/no_references_are_never_null/
    - https://stackoverflow.com/questions/4364536/is-a-null-reference-possible

4 days ago

On Thursday, 17 April 2025 at 17:36:49 UTC, Dave P. wrote:

>
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?

+1

>

Who is going to write that all over their code?

Nobody.