Jump to page: 1 24  
Page
Thread overview
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
January 25, 2023

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.

January 25, 2023

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 ?

January 25, 2023

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.

January 25, 2023

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.)

January 25, 2023

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.")

January 25, 2023

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

January 25, 2023

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.

January 26, 2023

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.

January 28, 2023

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

January 29, 2023

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