April 13
On 4/13/2024 3:24 AM, Dukc wrote:
> On Friday, 12 April 2024 at 20:43:50 UTC, Walter Bright wrote:
>> https://github.com/WalterBright/documents/blob/984374ca885e1cb10c2667cf872aebc13b4c1663/varRef.md
> 
> This hits the same problem my old [DIP1022](https://github.com/dlang/DIPs/blob/master/DIPs/other/DIP1022.md) was addressing. What should happen if `ref` variable is initialised with a RValue?

An error. The DIP says no rvalue references.


> In my opinion it should be an error, but:
>   - for `foreach` variables it is not

I think this is incorrect. Perhaps you meant static foreach?

>   - What if it's inside a template and is meant to be `ref` if possible but not otherwise?
> The answer is the same as my DIP proposed: allowing `auto ref` for the variables.

Auto ref would be a separate thing. Let's keep this focused on this DIP.


> Is `ref` allowed for static / global / `shared` storage class values?

At the moment, no. I'm not sure there's a compelling purpose for it. Many times I've encountered a good use for local ref variables, but never for statics.


> All in all this is something that would sometimes be nice, and doesn't immediately make me come up with any reason it wouldn't work. However it allows to declare two variables that share memory, which gives me a feeling there might be some loophole that I missed.

That's what @live addresses.


> Hopefully Timon will have a look. If my worry turns out to be unfounded, I tend to be in favour of this.

Thank you.

April 13
On 4/12/2024 3:13 PM, Richard (Rikki) Andrew Cattermole wrote:
>> Scope would apply to what the ref points to, not what the ref is.
> So this is introducing a non-transitive scope.

That's always been true of scope.


> This sounds like a consequence due to not having scope on the variable.

That's correct.

> Hmm, non-transitive scope, now that I'm thinking about this, it seems like the limitation is in the lack of DFA, not in the type system. Another thing for type state analysis DFA I suppose.

I don't think it's possible to do that for data structures.

> Ugh what?
> 
> wrapper died, item still alive.
> 
> You shouldn't be able to access item after that.
> 
> This would likely be the primary use case to using ref on a variable declaration and people will try it as soon as they learn they can put ref on a variable.

Having the data structure control its contents is the usual method to deal with that.


> Borrowing memory from other variables will be attempted, and will be attempted often and that will cause frustration as an obvious feature isn't supported.

This is not a proposal to add DFA.

April 14
On 14/04/2024 4:15 AM, Walter Bright wrote:
> On 4/12/2024 3:13 PM, Richard (Rikki) Andrew Cattermole wrote:
>> Borrowing memory from other variables will be attempted, and will be attempted often and that will cause frustration as an obvious feature isn't supported.
> 
> This is not a proposal to add DFA.

Indeed.

What I'm seeing is that we are limiting ourselves in designs so that things that people will expect to work, will not work. Simply because the compiler doesn't have a DFA capable of verifying program security.

For instance here is Atila saying how returning by ref is good because a variable cannot be declared as ref.

https://forum.dlang.org/post/ebytqnxztjsbtxtcnzke@forum.dlang.org

Except with this DIP this is no longer the case.

And one example from 2021 that shows someone wanting it.

https://forum.dlang.org/post/mtefqmzpefkuehliodfd@forum.dlang.org

Someone from C++ wanting to do exactly as I am suggesting 2013.

https://forum.dlang.org/post/mailman.700.1370235898.13711.digitalmars-d-learn@puremagic.com

I know there have been more discussions about this over the years even if I didn't find them.
April 13

On Saturday, 13 April 2024 at 16:12:29 UTC, Walter Bright wrote:

>

On 4/13/2024 3:24 AM, Dukc wrote:

>

What should happen if ref variable is initialised with a RValue?

An error. The DIP says no rvalue references.

Good so far...

> >

In my opinion it should be an error, but:
 - for foreach variables it is not

I think this is incorrect. Perhaps you meant static foreach?

No I didn't. This compiles and does nothing:

@safe void main()
{
    foreach(ref el; 0 .. 5) el = 42;
}

...which means that if ref int = 42; fails to compile, as it should, it's inconsistent with the present behaviour!

> >

Is ref allowed for static / global / shared storage class values?

At the moment, no. I'm not sure there's a compelling purpose for it. Many times I've encountered a good use for local ref variables, but never for statics.

No problem - but the dip should explicitly say it's for local variables then, not for variables in general.

> >

However it allows to declare two variables that share memory, which gives me a feeling there might be some loophole that I missed.

That's what @live addresses.

To be clear I'm happy with variables sharing memory, if it doesn't break type safety in any way. I just have a suspicion that if it does so, it might be hard to notice at least for me.

April 13
On 4/13/2024 11:22 AM, Dukc wrote:
> This compiles and does nothing:
> 
> ```D
> @safe void main()
> {
>      foreach(ref el; 0 .. 5) el = 42;
> }
> ```

https://issues.dlang.org/show_bug.cgi?id=24499


> No problem - but the dip should explicitly say it's for local variables then, not for variables in general.

The DIP does say:

"There doesn't appear to be a downside of using ref for such declarations, so by extension ordinary local variables should also benefit from being declared as ref."

Though it could be made more explicit.

April 14

On Friday, 12 April 2024 at 20:43:50 UTC, Walter Bright wrote:

>

https://github.com/WalterBright/documents/blob/984374ca885e1cb10c2667cf872aebc13b4c1663/varRef.md

I am not exactly sure what is this solving, or how it improves code. And DIP draft does not exactly explain that either.

I am afraid D code will then become C++-like, and be riddled with all these refs.

For the last 20 years I was actively avoiding using references in C++ code, (with only exception of const& being ok, only because this avoids -> in C++, and implicitly in general tells me that pointer ownership is not being transferred to the called function). And I have seen many companies with large code bases doing the same. One of the main reasons for me to avoid it, is that it is obstructing call site by making things implicit. One could say similar things about lazy, but I think that is fine, as it is used in way more narrow context. Another reason to avoid are reassignment semantics, and complex interactions with construction, destruction, which are non trivial.

From all C++ devs that I do know in professional setting (most of them 10-20 years writing C++ code every day), in fact not a single one of them, know exact semantics of C++ references. And anybody who said they do for sure, in fact didn't.

In C++, I didn't write a single ref variable in my life (by choice), other than while debugging others code. And not a single (non-const) ref parameter. And it never was limiting me or caused readability problems (quite the contrary actually).

(Usage of & in lambda capture is fine, as there is no other way, and this is useful. But in D, things are currently inferred automatically by compiler. Plus in lambda capture semantic is pretty clear and very rarely abused in a way that makes code hard to follow).

I would say that ref variables is the worst C++ feature from all its "features".

In C++, they are kind of needed sometimes, i.e. for example when implementing mutable containers, to implement things like x[foo] += 1, but it can be abused (i.e. if x[foo] is assigned to auto &z somewhere first, then one mutates x in a way, that z becomes invalid reference, and only then you access z. But in D, we have nicer and more power ways to deal with things like x[foo] += 1 using op* family of function. So we do not need these kinds of references most of the time. (I do know some people do use non-const ref return values for functions in D. But it is not exactly needed most of the time, or can be implemented using wrapper structs with some kind of forwarding if really really required).

As of safety. I would say references, as they are in C++, make C++ less safe. Exactly because of extra complications, implicitness, and other issues.

In D, readability is even less of an issue for pointers, as -> is not required, and one will use a ., which is nice, and one will sporadically see maybe something like (*x) =, or similar (rarely, as this is usually used for out parameters for returning multiple values, but that can be easily done with out or some multi-value return / tuple return maybe). And using *x = is in fact pretty nice to remind you (and code reviewer, or future you) what is happening exactly.

I am not totally against (it is a choice to use or not use this future), but if it proliferates into libraries (or phobos!), I would be rather disappointed for me personally.

And yes, I do like and use const ref in D sometimes (i.e. for fixed arrays, or some structs), and sporadically ref in foreach which is handy. Still I not needed, I don't. I.e. I trust compiler to not copy some structs in array if I do not modify them for example.

And for classes everything is by "ref" (the object itself, not the variable), so that mostly avoid copying issues, that C++ needs to deal with.

More concretely. Example you show

ref int dark(ref int x, int i) {
    ref j = i;     // j now points to i
    j = 3;         // now i is 3 as well

...

and I am already lost basically. Lets say one has very slightly modified same code:

struct A {
  ~this() {
     ....
  }
}

ref int dark(ref A x, A i) {
    ref j = i;     // j now points to i
    j = A(3);         // now i is 3 as well

...

And now, I need to think very hard to know what is actually happening. Or even miss the fact that my assignment probably caused call to As destructor. And god forgive there were other references (by pointers) to object in j previously.

I do not like implicit mechanisms that are not easy to track without extra context.

And I do fail to see why one would complicating language and implementation for very small gain, where in very rare cases where one there would be benefit, doing it explicitly using pointers is in fact better and cleaner by being explicit. If compiler could fully prove correctness, than maybe I could see some benefit, but good luck with that.

You can also see languages like Go, where there are pointers, and no references. And they are perfectly happy with that, with gazillion of libraries showing it is not really an issue.

Regards.

April 14

On Friday, 12 April 2024 at 20:43:50 UTC, Walter Bright wrote:

>

https://github.com/WalterBright/documents/blob/984374ca885e1cb10c2667cf872aebc13b4c1663/varRef.md

It would help if you include an example where such reference variable makes the code better / enables writing nicer code. One example usecase that I can come up with quickly:

struct A {...}

ref A foo();

void bar() {
    A* a = foo(); // not allowed
    A* a = &foo(); // workaround
    a.modifySomething(100);

    A b = foo(); // makes a copy
    b.modifySomething(100); // does not modify the thing returned by foo.

    auto c = foo(); // makes a copy

    ref d = foo(); // avoids copy
    d.modifySomething(100); // operates on the thing returned by foo
                            // instead of on a copy
}

In this case, the reference variable avoids having to use a pointer. Not a huge gain, but it helps a little (no need to worry about null or about changing the pointer itself).

Another comment: please be exhaustive in examples, in order to define clearly and explicitly the semantics. Are these allowed and if yes what do they do?

ref int i = ...
ref j = i;
j = 1; // ?
void foo(ref int i) {
    ref j = i;
    j = 1; // ?
}
void foo(ref int i);

ref int j = ...
foo(j); // ?
int* foo();

ref int i = *foo(); // ?
ref int i = ...
auto j = i; // typeof(j)?
auto p = &i; // typeof(p)?
// This is allowed in C++, with different syntax of course.
struct S {
    ref int j;

    S(ref int i) {
        j = i;
    }
    // Default constructor is deleted, because j must be initialized.
}

int a;
S aa = S(a);
(ref int)[3] array_of_refs; // (const int*)[3] arr; is currently not allowed, so this case probably neither.
April 14
I appreciate your thoughts.

I agree that ref parameters make it slightly less clear what is happening at the call site. But we don't really put documentation at the call site, we put the documentation at the callee. A ref parameter self-documents restrictions on how the pointer is used. `out` goes even further, it says that a value is not being passed in, only out. Pointers come with other behaviors - they can be incremented, and they can be free'd.

In my years of experience, I see a lot of operating system APIs that use a pointer. It's often unclear what is going to happen with that pointer. Most of the time, they stick to ref semantics, but not always. Like a char* being actually a string in C, rather than a reference to a char.

Just recently, I submitted a PR to replace some pointers in the DMD source code with refs, like this one:

https://github.com/dlang/dmd/pull/16379/files#diff-d58f278dd62016ccc4d0723aab00357f640541f027b68df715511d8e36c8115dR1773

Note how much simpler the line becomes, though it does exactly the same thing.

I've come to appreciate this difference.

And the more that arrays and refs can replace raw pointer use, the more memory safe the code is. Ref also makes it easy to switch a parameter from value <=> ref, for trying out which is faster.

It's worthwhile to extend the ability to use refs instead of pointers.

BTW, although Go has pointers, it does not allow pointer arithmetic. That makes them much like D's ref types.
April 15

On Sunday, 14 April 2024 at 00:04:13 UTC, Witold Baryluk wrote:

>

I would say that ref variables is the worst C++ feature from all its "features".

Unless necessary, C++ actually does not advocate pointers. The various facilities in C++ allow you to avoid pointers. Pointers are too low-level.
References in C++ is very common.
I think it's best to find a simpler way to express reference in D, or use it like C++'s &, otherwiseref would be too ugly.

April 15

On Friday, 12 April 2024 at 20:43:50 UTC, Walter Bright wrote:

>

https://github.com/WalterBright/documents/blob/984374ca885e1cb10c2667cf872aebc13b4c1663/varRef.md

Returning a ref variable from a function that returns a ref is not allowed. I.e. a ref cannot be assigned to a ref with a scope that exceeds the scope of the source.

This seems like an unnecessary restriction. From experience, unnecessary restrictions always seem to come back to bite us. Yes, it can be added later, but this seems like a no brainer.

  1. A ref variable cannot be rebound. Which means that at declaration time, you know what it points to and will always point to. The lifetime of the new ref variable can assume the lifetime of the thing it is assigned to.
  2. This would not require flow analysis, since the inputs are immutable and known at the time semantic is run for the declaration.
  3. DIP 1000 already allows such lifetime forwarding:
void foo(ref int x)
{
    // ref int y = x;
    // return y;
    // same as:
    ref int bar(ref int x2) {
        return x2;
    }
    return bar(x);
}

Other than that, this looks pretty good!

-Steve