Search
Page
RfC for language feature: rvalue struct
Jan 25, 2023
FeepingCreature
Jan 25, 2023
Basile B.
Jan 25, 2023
FeepingCreature
Jan 25, 2023
Paul Backus
Jan 25, 2023
FeepingCreature
Jan 25, 2023
Paul Backus
Jan 25, 2023
TheZipCreator
Jan 26, 2023
FeepingCreature
Jan 28, 2023
Salih Dincer
Jan 29, 2023
FeepingCreature
Jan 29, 2023
Salih Dincer
Jan 29, 2023
FeepingCreature
Jan 30, 2023
Salih Dincer
Jan 30, 2023
FeepingCreature
Jan 30, 2023
Dukc
Jan 30, 2023
FeepingCreature
Jan 30, 2023
Dukc
Jan 30, 2023
FeepingCreature
Jan 30, 2023
FeepingCreature
Jan 30, 2023
Dukc
Jan 31, 2023
FeepingCreature
Jan 31, 2023
FeepingCreature
Jan 31, 2023
Dukc
Jan 31, 2023
FeepingCreature
Jan 31, 2023
H. S. Teoh
Feb 01, 2023
FeepingCreature
Feb 01, 2023
FeepingCreature
Feb 01, 2023
FeepingCreature
Feb 01, 2023
Jack Applegame
Feb 01, 2023
Tim

Before I take on the effort of writing up and submitting a DIP, let me solicit feedback and see if anyone can see a reason why this idea is dumb and doesn't work.

tl;dr: `immutable struct` was a mistake: it's too weak. `rvalue struct` is what we really want.

# aside: what are lvalues and rvalues

lvalue: an expression that has an address.
rvalue: an expression that does not.

The names come from `a = b`: an "lvalue" (left value) is a thing that can appear on the left of the equals sign, an "rvalue" (right value) may only appear on the right.

Example: `int a` is an lvalue. `5` is an rvalue. You can write `a = 5` but not `5 = a`.

# rvalue struct

This is a summary of an idea from my DConf talk https://www.youtube.com/watch?v=eGX_fxlig8I

I'm writing it up because I'm noticing that the `immutable` bugs are neverending and the workarounds are an endless hole into which effort and nerves are thrown to no perceivable change.

What is it? Take this struct

``````rvalue struct S
{
int a;
int[] b;
}
``````

Two rules:

1. A `rvalue` struct has `immutable` fields.
`typeof(s)` is `S`, but `typeof(s.b)` is `immutable int[]`.
2. Any symbol that would be an lvalue to a field of `S` is instead an rvalue.

That's it.

What is the effect of this?

This works:

``````S foo(S s) {
S value = S.init;
value = s;
*&value = s;
return s;
}
``````

This does not:

``````S s;
s.a = 5;
&s.a
((ref int i) {})(s.a);
``````

Note that you can overwrite `value` all you want. You can take the address of `value`. It's not immutable. But its fields are immutable. Isn't that a problem? No: because its fields don't exist. They're rvalues. You can't address them, you can't reference them. You can never observe a constness violation on them, because you can only observe them as rvalues. `s.a` is effectively an accessor.

# What's the use?

D libraries like to behave like they can declare variables and assign values to them. (Oh, to live in such innocence!) This is all over Phobos, Dub, etc. `immutable struct` frustrates this belief. Because you could always take the address of a field, which would be `immutable T*`, you could see the value changing when you overwrite the variable - a constness violation. `immutable` solves this by preventing you from modifying the memory of the field while the pointer lives. This largely doesn't work, because people don't test with `immutable struct` in the first place. If an `rvalue struct` is used, the naive code works as before, but the type gets the correctness benefits of immutable: you can only construct a new value through the constructor.

# Corporate Motivation

Some details about Funkwerk, fresh off `wc -l`: In the modern part of our codebase, we have 648 domain structs, out of which 278 are `immutable struct`, and at least another 129 are good candidates to make immutable. In those 113kloc, we have no pointers to struct fields. None. `immutable struct` puts on the language, on library developers and on end users, immense difficulty and effort to protect a usecase that isn't useful.

On Wednesday, 25 January 2023 at 16:23:51 UTC, FeepingCreature wrote:

>

Before I take on the effort of writing up and submitting a DIP, let me solicit feedback and see if anyone can see a reason why this idea is dumb and doesn't work.
[...]

Sometimes I'm thinking about `rvalue` as a new storage class. The problem with that is that rvalues data would be logical rvalues. Structures or static arrays cannot generally be physical rvalues, that does not work, allocas are required.

But I think it's pretty clear for you already, right ?

On Wednesday, 25 January 2023 at 16:23:51 UTC, FeepingCreature wrote:

>

Before I take on the effort of writing up and submitting a DIP, let me solicit feedback and see if anyone can see a reason why this idea is dumb and doesn't work.

tl;dr: `immutable struct` was a mistake: it's too weak. `rvalue struct` is what we really want.

[...]

I agree that `immutable struct` is a mistake. It's a weird special case in the language, and because of that most D code is not prepared to deal with it.

I am not convinced that the solution to this is to introduce a new language feature that is even weirder and specialer.

On Wednesday, 25 January 2023 at 17:28:10 UTC, Paul Backus wrote:

>

On Wednesday, 25 January 2023 at 16:23:51 UTC, FeepingCreature wrote:

>

Before I take on the effort of writing up and submitting a DIP, let me solicit feedback and see if anyone can see a reason why this idea is dumb and doesn't work.

tl;dr: `immutable struct` was a mistake: it's too weak. `rvalue struct` is what we really want.

[...]

I agree that `immutable struct` is a mistake. It's a weird special case in the language, and because of that most D code is not prepared to deal with it.

I am not convinced that the solution to this is to introduce a new language feature that is even weirder and specialer.

I mean, I think this is the problem. The only thing about `rvalue struct` that is "weird" and "special" is that it isn't the default. D opts you in, without your consent or asking, to a huge amount of language power when it comes to manipulating values. If you declare a variable, you can assign it, reassign it, take its address, access fields, take the address of arbitrary subfields... That works fine in a language like C, that deliberately makes no guarantees about anything, and if we want to go that way we may as well remove `private` and go back to monke. But D2 tries to have its cake and eat it too, to let you make code safe and predictable, while still opting you into mutability and referenceability and thus actually making some tasks straight up impossible (without heavy casting).

`rvalue` is not a unique and special snowflake, but an attempt to return one tiny part of the language to simplicity and sanity. (If it were up to me, every struct and every variable would be `rvalue` unless you explicitly declared it otherwise.)

On Wednesday, 25 January 2023 at 17:47:34 UTC, FeepingCreature wrote:

>

I mean, I think this is the problem. The only thing about `rvalue struct` that is "weird" and "special" is that it isn't the default. D opts you in, without your consent or asking, to a huge amount of language power when it comes to manipulating values. If you declare a variable, you can assign it, reassign it, take its address, access fields, take the address of arbitrary subfields... That works fine in a language like C, that deliberately makes no guarantees about anything, and if we want to go that way we may as well remove `private` and go back to monke. But D2 tries to have its cake and eat it too, to let you make code safe and predictable, while still opting you into mutability and referenceability and thus actually making some tasks straight up impossible (without heavy casting).

`rvalue` is not a unique and special snowflake, but an attempt to return one tiny part of the language to simplicity and sanity. (If it were up to me, every struct and every variable would be `rvalue` unless you explicitly declared it otherwise.)

I agree that if we could start with non-refrenceability as the default and make referenceability opt-in, that would be a cleaner design than D's referenceability-by-default. Unfortunately, that option is not on the table, at least not for D.

For D, the options actually available to us are "implement `rvalue struct` as an opt-in special case" or "don't do that." I remain unconvinced that the former is better language design than the latter.

(Another way to put this is that "returning one tiny part of the language to simplicity and sanity" is worse than leaving the whole language consistently "insane.")

On Wednesday, 25 January 2023 at 17:09:18 UTC, Basile B. wrote:

>

On Wednesday, 25 January 2023 at 16:23:51 UTC, FeepingCreature wrote:

>

Before I take on the effort of writing up and submitting a DIP, let me solicit feedback and see if anyone can see a reason why this idea is dumb and doesn't work.
[...]

Sometimes I'm thinking about `rvalue` as a new storage class. The problem with that is that rvalues data would be logical rvalues. Structures or static arrays cannot generally be physical rvalues, that does not work, allocas are required.

But I think it's pretty clear for you already, right ?

Yeah you need to pass it as a pointer for methods and such, but I want to even go so far as to say that methods on an rvalue struct should see `this` as rvalue. (Except constructors, I guess?)

On Wednesday, 25 January 2023 at 16:23:51 UTC, FeepingCreature wrote:

>

Before I take on the effort of writing up and submitting a DIP, let me solicit feedback and see if anyone can see a reason why this idea is dumb and doesn't work.

[...]

I mean, couldn't you just do this?

``````struct Foo {
immutable:
int bar;
int[] baz;
}
``````

Unless I'm missing something, I don't really see why an entirely new language construct is required.

On Wednesday, 25 January 2023 at 23:56:20 UTC, TheZipCreator wrote:

>

I mean, couldn't you just do this?

``````struct Foo {
immutable:
int bar;
int[] baz;
}
``````

Unless I'm missing something, I don't really see why an entirely new language construct is required.

The problem is that then you can't do

``````Foo foo;
foo = Foo(2, [3]);
``````

And that may look easy to avoid, but there's a plethora of bugs where for instance Phobos does just that.

On Thursday, 26 January 2023 at 03:39:14 UTC, FeepingCreature wrote:

>

The problem is that then you can't do

``````Foo foo;
foo = Foo(2, [3]);
``````

And that may look easy to avoid, but there's a plethora of bugs where for instance Phobos does just that.

No problem:

``````struct Foo(T)
{
import std.conv   : to;
import std.traits : ImmutableOf;

this(R)(R bar, R[] baz)
{
this.bar = bar;
this.baz = baz.to!(ImmutableOf!T[]);
}

immutable:
T bar;
T[] baz;
}

void main()
{
immutable arr = [9, 8, 7];
auto num1 = Foo!int(1, [2, 3, 4]);
auto num2 = Foo!int(1, arr);

string str = "bcd";
auto str1 = Foo!char('a', str.dup);
auto str2 = Foo!char('a', str);
}
``````

SDB@79

On Saturday, 28 January 2023 at 19:35:49 UTC, Salih Dincer wrote:

>

On Thursday, 26 January 2023 at 03:39:14 UTC, FeepingCreature wrote:

>

The problem is that then you can't do

``````Foo foo;
foo = Foo(2, [3]);
``````

And that may look easy to avoid, but there's a plethora of bugs where for instance Phobos does just that.

No problem:

``````struct Foo(T)
{
import std.conv   : to;
import std.traits : ImmutableOf;

this(R)(R bar, R[] baz)
{
this.bar = bar;
this.baz = baz.to!(ImmutableOf!T[]);
}

immutable:
T bar;
T[] baz;
}

void main()
{
immutable arr = [9, 8, 7];
auto num1 = Foo!int(1, [2, 3, 4]);
auto num2 = Foo!int(1, arr);

string str = "bcd";
auto str1 = Foo!char('a', str.dup);
auto str2 = Foo!char('a', str);
}
``````

SDB@79

No, I mean

``````num1 = num2;
onlineapp.d(22): Error: cannot modify struct instance `num1` of type `Foo!int` because it contains `const` or `immutable` members
``````
« First   ‹ Prev
1 2 3 4