www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - RfC for language feature: rvalue struct

reply FeepingCreature <feepingcreature gmail.com> writes:
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.



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



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.



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.



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.
Jan 25 2023
next sibling parent reply Basile B. <b2.temp gmx.com> writes:
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 ?
Jan 25 2023
parent FeepingCreature <feepingcreature gmail.com> writes:
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?)
Jan 25 2023
prev sibling next sibling parent reply Paul Backus <snarwin gmail.com> writes:
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.
Jan 25 2023
parent reply FeepingCreature <feepingcreature gmail.com> writes:
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.)
Jan 25 2023
parent Paul Backus <snarwin gmail.com> writes:
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.")
Jan 25 2023
prev sibling next sibling parent reply TheZipCreator <thezipcreator protonmail.com> writes:
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? ```d 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.
Jan 25 2023
parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Wednesday, 25 January 2023 at 23:56:20 UTC, TheZipCreator 
wrote:
 I mean, couldn't you just do this?

 ```d
 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.
Jan 25 2023
parent reply Salih Dincer <salihdb hotmail.com> writes:
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: ```d 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
Jan 28 2023
parent reply FeepingCreature <feepingcreature gmail.com> writes:
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: ```d 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 ``` ```
Jan 28 2023
parent reply Salih Dincer <salihdb hotmail.com> writes:
On Sunday, 29 January 2023 at 03:29:45 UTC, FeepingCreature wrote:
 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
 ```
Sorry for writing an answer without doing the necessary unit tests. I forgot to put the type in parentheses. Here is the corrected version: ```d struct S(T) { import std.conv : to; import std.traits : ImmutableOf; immutable(T)[] data; this(R)(R[] data) { this.data = data.to!(ImmutableOf!T[]); } string toString() const { import std.format : format; return format("%s: %s", typeid(data), data); } } unittest { S!char test1, test2; alias TestType = typeof(test1.data); import std.traits : isSomeString; assert(is(TestType == string) && isSomeString!TestType); test1 = S!char("bcd"); test1.data ~= "234"; test2 = test1; assert(test1 == test2); } void main() { import std.stdio; string str = "bcd"; auto str1 = S!char(str.dup); auto str2 = S!char("abc"); str1 = str2; str1.writeln; // const(immutable(char)[]): abc str2.writeln; // const(immutable(char)[]): abc } ``` SDB 79
Jan 29 2023
parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Sunday, 29 January 2023 at 17:41:26 UTC, Salih Dincer wrote:
 On Sunday, 29 January 2023 at 03:29:45 UTC, FeepingCreature 
 wrote:
 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
 ```
Sorry for writing an answer without doing the necessary unit tests. I forgot to put the type in parentheses. Here is the corrected version: ```d struct S(T) { import std.conv : to; import std.traits : ImmutableOf; immutable(T)[] data; this(R)(R[] data) { this.data = data.to!(ImmutableOf!T[]); } string toString() const { import std.format : format; return format("%s: %s", typeid(data), data); } } unittest { S!char test1, test2; alias TestType = typeof(test1.data); import std.traits : isSomeString; assert(is(TestType == string) && isSomeString!TestType); test1 = S!char("bcd"); test1.data ~= "234"; test2 = test1; assert(test1 == test2); } void main() { import std.stdio; string str = "bcd"; auto str1 = S!char(str.dup); auto str2 = S!char("abc"); str1 = str2; str1.writeln; // const(immutable(char)[]): abc str2.writeln; // const(immutable(char)[]): abc } ``` SDB 79
I think you're missing the concept a bit: ``` test1 = S!char("bcd"); test1.data ~= "234"; This should emphatically *not* work. You're mutating a field of `test1`. That's what we don't want to ever happen! Instead, you should have to write `test1 = test1(test1.data ~ "234");` This looks extremely similar, but the key is that the assignment has to go through the constructor. That way, `test1` can only go from "valid value" to "valid value".
Jan 29 2023
parent reply Salih Dincer <salihdb hotmail.com> writes:
On Sunday, 29 January 2023 at 20:48:11 UTC, FeepingCreature wrote:
 This should emphatically *not* work. You're mutating a field of 
 `test1`. That's what we don't want to ever happen!

 Instead, you should have to write `test1 = test1(test1.data ~ 
 "234");`

 This looks extremely similar, but the key is that the 
 assignment has to go through the constructor. That way, `test1` 
 can only go from "valid value" to "valid value".
In order to use it as you say, the following opCall() should be implemented: ```d this(R)(R[] data) { opCall(data); } alias opCall this; property opCall() inout { return data; } property opCall(R)(R[] data) { return this.data = data.to!(ImmutableOf!T[]); } ``` Then it works like this: ```d immutable arr = [ 1, 2, 3]; auto num1 = S!int(arr.dup); auto num2 = S!int(arr); assert(is(typeof(num1) == S!int)); assert(is(typeof(num1.data) == immutable(int)[])); num1(num1 ~ 4); // or: num1 = num1 ~ 5; assert(num1.length == 5); assert(num1.length > num2.length); num2 = num1; assert(num1.length == num2.length); ``` But `num1 ~= 6` doesn't work because it doesn't have an overload:
 Error: cannot append type `int` to type `S!int`
SDB 79
Jan 29 2023
parent FeepingCreature <feepingcreature gmail.com> writes:
On Monday, 30 January 2023 at 03:09:14 UTC, Salih Dincer wrote:
 But `num1 ~= 6` doesn't work because it doesn't have an 
 overload:

 Error: cannot append type `int` to type `S!int`
SDB 79
But what prevents `num1.field ~= 6`? Making it private and giving it an accessor. And then you're at this: ``` struct Struct { ConstRead private int[] field_; mixin(GenerateAll); } ``` Which is *exactly* what we're trying to get away from because it's too much overhead! So I appreciate your suggestions, but I hope it's clear that they don't compete with ``` rvalue struct Struct { int[] field; } ```
Jan 29 2023
prev sibling next sibling parent reply Dukc <ajieskola gmail.com> writes:
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.
Isn't this what private/` system` (DIP1035) fields with public/` trusted` getter functions are for?
Jan 30 2023
parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Monday, 30 January 2023 at 13:53:53 UTC, Dukc 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.
Isn't this what private/` system` (DIP1035) fields with public/` trusted` getter functions are for?
Yes, the behavior of "private fields with public getters" is exactly what we're aiming for. (` system` - not really; we're not interested in doing un ` safe` things to them. This is purely about functional programming style.) Having an in-language way to declare "immutable rvalue everything" would pose some advantages over getters though: - we wouldn't have to figure out when we'd need to dup a field on access to avoid mutation-at-a-distance: sSince immutable is transitive, the answer is "never", which also makes the GC happy - we avoid the template overhead of having to generate accessors for every field, with all the required analysis to figure out attributes etc. - even struct methods cannot mutate fields, for maximum purity - it just looks nicer.
Jan 30 2023
parent reply Dukc <ajieskola gmail.com> writes:
On Monday, 30 January 2023 at 14:10:39 UTC, FeepingCreature wrote:
 - we wouldn't have to figure out when we'd need to dup a field 
 on access to avoid mutation-at-a-distance: sSince immutable is 
 transitive, the answer is "never", which also makes the GC happy
This can be accomplished by making return value of the getter `const`.
 - we avoid the template overhead of having to generate 
 accessors for every field, with all the required analysis to 
 figure out attributes etc.
 - even struct methods cannot mutate fields, for maximum purity
 - it just looks nicer.
These are all true. However, the proposed feature just feels inconsistent with how the rest of the language works. I can't pinpoint exactly why, but I think it has to do with that Immutability and visibility are usually treated as separate issues. This just does not play togeteher with rest of the language. To be fair, it wouldn't be the first feature to be like that. `protected` and `lazy` for example also feel out of place IMO. I feel you use D very differently than I do. I bump to these issues rarely enough that I don't feel bothered at all to write getters when I do. Plus I consider the struct itself (and rest of the module) being able to modify the field mostly a good thing. I wonder how people feel in general. If your style is widely spread, there might indeed be a case for a language feature.
Jan 30 2023
parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Monday, 30 January 2023 at 14:43:21 UTC, Dukc wrote:
 I feel you use D very differently than I do. I bump to these 
 issues rarely enough that I don't feel bothered at all to write 
 getters when I do. Plus I consider the struct itself (and rest 
 of the module) being able to modify the field mostly a good 
 thing. I wonder how people feel in general. If your style is 
 widely spread, there might indeed be a case for a language 
 feature.
That's fair. We're coming at D like it's a functional language at heart with mutability bolted on, rather than a mutable language which can borrow some functional idioms. See my 2019 DConf talk https://www.youtube.com/watch?v=nKMOFaAdtAc ; the basic summary of how we use the language still holds. The key insight is that we treat D like a functional language at the domain level, like an object-oriented language at the application level, and autogenerate everything at the infrastructure level. Almost all our structs live at the domain level, meaning they don't actually own state, they just represent domain knowledge. State-ownership happens at the application level, ie. in classes. I do feel like `std.algorithm`, `std.functional` and ranges in general invite this sort of pure-data modelling though.
Jan 30 2023
parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Monday, 30 January 2023 at 15:21:02 UTC, FeepingCreature wrote:
 The key insight is that we treat D like a functional language 
 at the domain level, like an object-oriented language at the 
 application level, and autogenerate everything at the 
 infrastructure level. Almost all our structs live at the domain 
 level, meaning they don't actually own state, they just 
 represent domain knowledge. State-ownership happens at the 
 application level, ie. in classes.
I need to emphasize this point. For instance, say that a struct represents "the arrival of a train at a station at a certain time." When we find that the train has a delay, we could mutate the `arrival.time` field on that struct. But what would that mean? The information given was *accurate* at the time. The struct, representing our knowledge at a certain point in time, is still valid. We have now gained new information, and wish to now replace it with an alternative value that has a different arrival time. But we don't want to *change the original,* ever, by any means! The original value is and remains a legitimate representation of the domain. That's why we don't use ref, or pointers, or state mutation in structs. Instead, we "patch" the variable with something like Haskell arrows [1]: ``` alias updateTimes = visit => visit .rebuild!(a => a.arrival = newArrivalTime) .rebuild!(a => a.departure = newDepartureTime); return visits.map!(visit => visit.id == visitId ? updateTimes(visit) : visit); ``` or some such. The goal of marking structs `immutable` (or `rvalue`) is to make any idiom that does not operate like this fail to compile. [1] https://en.wikibooks.org/wiki/Haskell/Understanding_arrows
Jan 30 2023
parent reply Dukc <ajieskola gmail.com> writes:
On Monday, 30 January 2023 at 15:31:59 UTC, FeepingCreature wrote:
 I need to emphasize this point. For instance, say that a struct 
 represents "the arrival of a train at a station at a certain 
 time." When we find that the train has a delay, we could mutate 
 the `arrival.time` field on that struct. But what would that 
 mean? The information given was *accurate* at the time. The 
 struct, representing our knowledge at a certain point in time, 
 is still valid. We have now gained new information, and wish to 
 now replace it with an alternative value that has a different 
 arrival time. But we don't want to *change the original,* ever, 
 by any means! The original value is and remains a legitimate 
 representation of the domain.
I see. The idea about immutability is good IMO, but your ways to achieve that are different from mine. My way would be to have the original struct be `immutable`, which protects the fields, but have the value under construction be mutable with public fields. Data pointed to by any arrays or pointers in the `struct` would still be `immutable`. I don't see a need for fields to be non-referenceable since immutability protects them if the data in question is already committed.
Jan 30 2023
parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Monday, 30 January 2023 at 20:15:48 UTC, Dukc wrote:
 I don't see a need for fields to be non-referenceable since 
 immutability protects them if the data in question is already 
 committed.
Yes, again, the idea is that rvalue allows *overwriting mutable variables* of this type. ``` rvalue struct S { int a; } S s; s = S(5); s = S(6); ``` Which `immutable` does not allow. And this is important because this idiom is inescapable, it will always crop up (all over std.algorithm causing compile errors for our immutable structs, cough), and in fact I increasingly think it's just *good.* It's an inherent part of D's C heritage.
Jan 30 2023
next sibling parent FeepingCreature <feepingcreature gmail.com> writes:
On Tuesday, 31 January 2023 at 07:31:58 UTC, FeepingCreature 
wrote:
 And this is important because this idiom is inescapable, it 
 will always crop up (all over std.algorithm causing compile 
 errors for our immutable structs, cough), and in fact I 
 increasingly think it's just *good.* It's an inherent part of 
 D's C heritage.
Just to give an example, `maxElement` is actually straight up impossible to implement for every type without either of: - `rvalue struct replacing `immutable struct` - const range API (`typeof(this) next()` rather than `void popFront()`) - rebindable structs in Phobos (librebindable) - mandatory tail recursion at every optimization level. None of which D has.
Jan 30 2023
prev sibling parent reply Dukc <ajieskola gmail.com> writes:
On Tuesday, 31 January 2023 at 07:31:58 UTC, FeepingCreature 
wrote:
 On Monday, 30 January 2023 at 20:15:48 UTC, Dukc wrote:
 I don't see a need for fields to be non-referenceable since 
 immutability protects them if the data in question is already 
 committed.
Yes, again, the idea is that rvalue allows *overwriting mutable variables* of this type.
In that case I'd probably go with a mutable pointer to an immutable struct. That way you can replace the value in whole, but not single fields of it.
Jan 31 2023
parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Tuesday, 31 January 2023 at 11:52:33 UTC, Dukc wrote:
 On Tuesday, 31 January 2023 at 07:31:58 UTC, FeepingCreature 
 wrote:
 On Monday, 30 January 2023 at 20:15:48 UTC, Dukc wrote:
 I don't see a need for fields to be non-referenceable since 
 immutability protects them if the data in question is already 
 committed.
Yes, again, the idea is that rvalue allows *overwriting mutable variables* of this type.
In that case I'd probably go with a mutable pointer to an immutable struct. That way you can replace the value in whole, but not single fields of it.
Okay, now how do you do that without causing GC load? :) Keep in mind that we plan to use this for very inner loops of not-always-cheap algorithms.
Jan 31 2023
next sibling parent "H. S. Teoh" <hsteoh qfbox.info> writes:
On Tue, Jan 31, 2023 at 01:02:57PM +0000, FeepingCreature via Digitalmars-d
wrote:
 On Tuesday, 31 January 2023 at 11:52:33 UTC, Dukc wrote:
 On Tuesday, 31 January 2023 at 07:31:58 UTC, FeepingCreature wrote:
 On Monday, 30 January 2023 at 20:15:48 UTC, Dukc wrote:
 I don't see a need for fields to be non-referenceable since
 immutability protects them if the data in question is already
 committed.
Yes, again, the idea is that rvalue allows *overwriting mutable variables* of this type.
In that case I'd probably go with a mutable pointer to an immutable struct. That way you can replace the value in whole, but not single fields of it.
Okay, now how do you do that without causing GC load? :) Keep in mind that we plan to use this for very inner loops of not-always-cheap algorithms.
Why would a mutable pointer to an immutable struct cause GC load? Isn't it just a matter of taking an address? struct S { ... } S s, t; immutable(S)* ptr = &s; ... ptr = &t; ... // etc T -- Truth, Sir, is a cow which will give [skeptics] no more milk, and so they are gone to milk the bull. -- Sam. Johnson
Jan 31 2023
prev sibling parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Tuesday, 31 January 2023 at 13:02:57 UTC, FeepingCreature 
wrote:
 Okay, now how do you do that without causing GC load? :) Keep 
 in mind that we plan to use this for very inner loops of 
 not-always-cheap algorithms.
Why can't you use regular encapsulation? I think it would be better for you to see if you can do this is as a rewrite instead as that doesn't affect the type system. to get slightly different value semantics, but as far as I can tell it is only a rewrite.
Jan 31 2023
next sibling parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Tuesday, 31 January 2023 at 18:13:58 UTC, Ola Fosheim Grøstad 
wrote:

 to get slightly different value semantics, but as far as I can 
 tell it is only a rewrite.
Or maybe it isn't (shrug). Anyway, you might want to look at the https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/records#non-destructive-mutation
Jan 31 2023
parent FeepingCreature <feepingcreature gmail.com> writes:
On Tuesday, 31 January 2023 at 18:30:44 UTC, Ola Fosheim Grøstad 
wrote:
 On Tuesday, 31 January 2023 at 18:13:58 UTC, Ola Fosheim 
 Grøstad wrote:

 "class" to get slightly different value semantics, but as far 
 as I can tell it is only a rewrite.
Or maybe it isn't (shrug). Anyway, you might want to look at https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/records#non-destructive-mutation
Yes, that's exactly what we want! What we use right now for `with` is ``` immutable struct S { int value; mixin(GenerateThis); } S s = S(5); S butFour = s.rebuild!(a => a.value = 4); ``` Which seems to work fine.
Jan 31 2023
prev sibling parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Tuesday, 31 January 2023 at 18:13:58 UTC, Ola Fosheim Grøstad 
wrote:
 On Tuesday, 31 January 2023 at 13:02:57 UTC, FeepingCreature 
 wrote:
 Okay, now how do you do that without causing GC load? :) Keep 
 in mind that we plan to use this for very inner loops of 
 not-always-cheap algorithms.
Why can't you use regular encapsulation? I think it would be better for you to see if you can do this is as a rewrite instead as that doesn't affect the type system. to get slightly different value semantics, but as far as I can tell it is only a rewrite.
Maybe it can be done as a rewrite? For instance, we used to do ``` struct S { private int a_; public int a() { return this.a_; } } ``` But this is not quite the semantic we actually want because, for instance, the accessors will still run the invariants, which is pointless because we know the fields won't ever change after the constructor call, and also the function call itself is just useless. But that's the interface we want, at least. The thing we *actually* want is, roughly, ``` struct S { private int a_; public rvalue alias a = this.a_; } ``` But then we're inventing features again. That said, even "a function that returns 'a'" isn't *quite* the right semantics, because this is valid D: ``` struct S { int a; } S foo() { ... } foo.a = 5; ``` On that topic, what the fuck, D.
Jan 31 2023
parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Wednesday, 1 February 2023 at 07:46:24 UTC, FeepingCreature 
wrote:
 Maybe it can be done as a rewrite?
[…]
 But this is not quite the semantic we actually want because, 
 for instance, the accessors will still run the invariants, 
 which is pointless because we know the fields won't ever change 
 after the constructor call, and also the function call itself 
 is just useless. But that's the interface we want, at least.
Hm, anyway, if you can do it as a rewrite then you should be able to implement it in the parser and in the runtime. That way you can maintain it even if it isn't added to the core D language. (If you are interested in something like this, just send me an email.)
Feb 01 2023
parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Wednesday, 1 February 2023 at 09:29:01 UTC, Ola Fosheim 
Grøstad wrote:
 On Wednesday, 1 February 2023 at 07:46:24 UTC, FeepingCreature 
 wrote:
 Maybe it can be done as a rewrite?
[…]
 But this is not quite the semantic we actually want because, 
 for instance, the accessors will still run the invariants, 
 which is pointless because we know the fields won't ever 
 change after the constructor call, and also the function call 
 itself is just useless. But that's the interface we want, at 
 least.
Hm, anyway, if you can do it as a rewrite then you should be able to implement it in the parser and in the runtime. That way you can maintain it even if it isn't added to the core D language. (If you are interested in something like this, just send me an email.)
I mean, since it's a *reduction* in capability, it would even be enough to do it as an audit step. Error on any assignment to a field in the struct marked with a UDA. That can't be done before semantic assignment though...
Feb 01 2023
parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Wednesday, 1 February 2023 at 11:26:17 UTC, FeepingCreature 
wrote:
 I mean, since it's a *reduction* in capability, it would even 
 be enough to do it as an audit step. Error on any assignment to 
 a field in the struct marked with a UDA.

 That can't be done before semantic assignment though...
Yes, if you can find a single location that you can hook into and then put all the required changes in separate (new) files to keep it maintainable. (The trick is to make as few changes as possible to files that keep changing upstream, otherwise rebasing becomes a tedious chore in my experience).
Feb 01 2023
prev sibling next sibling parent Jack Applegame <japplegame gmail.com> writes:
On Wednesday, 25 January 2023 at 16:23:51 UTC, FeepingCreature 
wrote:
 
Please, stop adding new features.
Jan 31 2023
prev sibling parent Tim <tim.dlang t-online.de> writes:
On Wednesday, 25 January 2023 at 16:23:51 UTC, FeepingCreature 
wrote:
 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.
Maybe escape analysis could be used as an alternative. If the compiler can prove that no pointer to a local variable or its members could escape, then it should be safe to reassign to the variable. With DIP1000 the compiler should be able to determine this.
Feb 01 2023