digitalmars.D - How to initialize immutable variables with an expression that throws
- FeepingCreature (21/21) Apr 02 2020 Consider the following code:
- Steven Schveighoffer (13/36) Apr 03 2020 Do it the old-fashioned way -- use casting ;)
- FeepingCreature (3/11) Apr 03 2020 Doesn't work - `immutable struct` means S is always immutable.
- Steven Schveighoffer (4/18) Apr 03 2020 totally misread your statement as making s immutable, not S.
- Atila Neves (20/41) Apr 03 2020 --------------------
- Steven Schveighoffer (21/72) Apr 03 2020 That's not the same. The original code does not call `call` with a
- =?UTF-8?Q?Ali_=c3=87ehreli?= (27/29) Apr 03 2020 How about using a Nullable!(immutable(S)):
- FeepingCreature (5/11) Apr 07 2020 Right, but that's still the exact same workaround, except with
- tsbockman (19/23) Apr 07 2020 The problem in the language is that immutability is considered a
- Max Samukha (3/13) Apr 07 2020 Exactly (with a small correction that the state is exited before
- H. S. Teoh (21/34) Apr 07 2020 [...]
- FeepingCreature (5/15) Apr 07 2020 That sounds like a way of thinking that can't be compatible with
- Nick Treleaven (21/24) Apr 09 2020 Sounds like making try an expression:
Consider the following code: struct S { } ... S s = void; try s = fun(); catch (Exception) return; call(s); Now you change S to be immutable. How are you supposed to initialize s? You can't assign to it anymore. Assume that we explicitly don't want to pull the call into the try body, for instance because we want exceptions from call to not be caught. The only way I've found is to make fun() return Algebraic!(S, Exception) but that's kind of ugly and bypasses a basic language feature. Maybe D could allow to initialize an immutable variable from the try{} body if the catch{} body is statically known to exit by return/throw?
Apr 02 2020
On 4/3/20 2:56 AM, FeepingCreature wrote:Consider the following code: struct S { } .... S s = void; try s = fun(); catch (Exception) return; call(s); Now you change S to be immutable. How are you supposed to initialize s? You can't assign to it anymore. Assume that we explicitly don't want to pull the call into the try body, for instance because we want exceptions from call to not be caught. The only way I've found is to make fun() return Algebraic!(S, Exception) but that's kind of ugly and bypasses a basic language feature. Maybe D could allow to initialize an immutable variable from the try{} body if the catch{} body is statically known to exit by return/throw?Do it the old-fashioned way -- use casting ;) S s_val = void; try s_val = fun(); catch (Exception) return; immutable s = s_val.assumeUnique; call(s); It's not as pretty and has a "code-by-assumption" issue. I wish you could just lambda-initialize this, but the return statement throws a wrench into that. -Steve
Apr 03 2020
On Friday, 3 April 2020 at 12:49:27 UTC, Steven Schveighoffer wrote:Do it the old-fashioned way -- use casting ;) S s_val = void; try s_val = fun(); catch (Exception) return; immutable s = s_val.assumeUnique; call(s);Doesn't work - `immutable struct` means S is always immutable.
Apr 03 2020
On 4/3/20 9:14 AM, FeepingCreature wrote:On Friday, 3 April 2020 at 12:49:27 UTC, Steven Schveighoffer wrote:totally misread your statement as making s immutable, not S. I don't know how to solve that problem correctly. -SteveDo it the old-fashioned way -- use casting ;) S s_val = void; try s_val = fun(); catch (Exception) return; immutable s = s_val.assumeUnique; call(s);Doesn't work - `immutable struct` means S is always immutable.
Apr 03 2020
On Friday, 3 April 2020 at 06:56:27 UTC, FeepingCreature wrote:Consider the following code: struct S { } ... S s = void; try s = fun(); catch (Exception) return; call(s); Now you change S to be immutable. How are you supposed to initialize s? You can't assign to it anymore. Assume that we explicitly don't want to pull the call into the try body, for instance because we want exceptions from call to not be caught. The only way I've found is to make fun() return Algebraic!(S, Exception) but that's kind of ugly and bypasses a basic language feature. Maybe D could allow to initialize an immutable variable from the try{} body if the catch{} body is statically known to exit by return/throw?-------------------- immutable struct S { } void main() { S impl() { try return fun(); catch (Exception) { S s = void; return s; } } call(impl); } void call(S s) { } S fun() { return S(); } --------------------
Apr 03 2020
On 4/3/20 9:21 AM, Atila Neves wrote:On Friday, 3 April 2020 at 06:56:27 UTC, FeepingCreature wrote:That's not the same. The original code does not call `call` with a void-initialized S. However, this might do the trick: void main() { bool voided = false; S impl() { try return fun(); catch (Exception) { voided = true; S s = void; return s; } } auto s = impl(); if(!voided) call(s); } But there is a catch here. Assigning an S to a void-initialized S is still copying stuff. It's not the same as never touching that memory. -SteveConsider the following code: struct S { } ... S s = void; try s = fun(); catch (Exception) return; call(s); Now you change S to be immutable. How are you supposed to initialize s? You can't assign to it anymore. Assume that we explicitly don't want to pull the call into the try body, for instance because we want exceptions from call to not be caught. The only way I've found is to make fun() return Algebraic!(S, Exception) but that's kind of ugly and bypasses a basic language feature. Maybe D could allow to initialize an immutable variable from the try{} body if the catch{} body is statically known to exit by return/throw?-------------------- immutable struct S { } void main() { S impl() { try return fun(); catch (Exception) { S s = void; return s; } } call(impl); } void call(S s) { } S fun() { return S(); } --------------------
Apr 03 2020
On 4/2/20 11:56 PM, FeepingCreature wrote:The only way I've found is to make fun() return Algebraic!(S, Exception) but that's kind of ugly and bypasses a basic language feature.How about using a Nullable!(immutable(S)): import std.typecons; alias NS = Nullable!(immutable(S)); NS makeS() { try { return NS(fun()); } catch (Exception) { return NS(); } } struct S { } S fun() { return S(); } void call(immutable(S) s) { } void foo() { auto ns = makeS(); if (!ns.isNull) { call(ns.get); } } void main() { foo(); } Ali
Apr 03 2020
On Friday, 3 April 2020 at 15:43:51 UTC, Ali Çehreli wrote:On 4/2/20 11:56 PM, FeepingCreature wrote:Right, but that's still the exact same workaround, except with Nullable instead of Algebraic. I know how to work around it. I'm saying what should be changed in the language so I don't *have to* work around it?The only way I've found is to make fun() return Algebraic!(S, Exception) but that's kind of ugly and bypasses a basic language feature.How about using a Nullable!(immutable(S)): [snip]
Apr 07 2020
On Tuesday, 7 April 2020 at 14:15:20 UTC, FeepingCreature wrote:Right, but that's still the exact same workaround, except with Nullable instead of Algebraic. I know how to work around it. I'm saying what should be changed in the language so I don't *have to* work around it?The problem in the language is that immutability is considered a permanent and fundamental property of a value, whereas it is actually a state that is entered after the initialization/construction of the value is complete, and exited when the value's memory is later reclaimed. Construction of an immutable value should be done by making a mutable variable, mutating it to the desired state via any arbitrary sequence of normal operations, and then declaring the mutable stage of its life cycle complete via `cast(immutable)` or something. The only problem with this is making the compiler smart enough to prove that no mutable references to the data are used after that point, so that the operation can be safe. The current rules for immutable constructors are sort of like this, but they're still overly strict to the point that I often need to make the constructor trusted and use (cast() this) to get the job done without excessive reliance on the optimizer, or excessive code duplication. Also, not everything can or should be done in a constructor.
Apr 07 2020
On Tuesday, 7 April 2020 at 19:52:36 UTC, tsbockman wrote:On Tuesday, 7 April 2020 at 14:15:20 UTC, FeepingCreature wrote:Exactly (with a small correction that the state is exited before the destruction if there is any)!Right, but that's still the exact same workaround, except with Nullable instead of Algebraic. I know how to work around it. I'm saying what should be changed in the language so I don't *have to* work around it?The problem in the language is that immutability is considered a permanent and fundamental property of a value, whereas it is actually a state that is entered after the initialization/construction of the value is complete, and exited when the value's memory is later reclaimed.
Apr 07 2020
On Tue, Apr 07, 2020 at 07:52:36PM +0000, tsbockman via Digitalmars-d wrote: [...]The problem in the language is that immutability is considered a permanent and fundamental property of a value, whereas it is actually a state that is entered after the initialization/construction of the value is complete, and exited when the value's memory is later reclaimed. Construction of an immutable value should be done by making a mutable variable, mutating it to the desired state via any arbitrary sequence of normal operations, and then declaring the mutable stage of its life cycle complete via `cast(immutable)` or something. The only problem with this is making the compiler smart enough to prove that no mutable references to the data are used after that point, so that the operation can be safe.[...] Actually, in the current language if you have a pure function that constructs a (mutable) value of type T, and the compiler can infer that the function returns a unique value, you can implicitly cast it to immutable: struct T { int x; float[] y; } T makeT(int x, float[] y) pure { return T(x, y); // N.B.: returns mutable } void main() { // N.B.: no need to cast: immutable(T) t = makeT(1, [ 2.3, 4.5 ]); } T -- Chance favours the prepared mind. -- Louis Pasteur
Apr 07 2020
On Tuesday, 7 April 2020 at 19:52:36 UTC, tsbockman wrote:On Tuesday, 7 April 2020 at 14:15:20 UTC, FeepingCreature wrote:That sounds like a way of thinking that can't be compatible with something like `immutable struct S {}`? Because https://issues.dlang.org/show_bug.cgi?id=20670 aside, S can't really be in a "mutable state", typewise.Right, but that's still the exact same workaround, except with Nullable instead of Algebraic. I know how to work around it. I'm saying what should be changed in the language so I don't *have to* work around it?The problem in the language is that immutability is considered a permanent and fundamental property of a value, whereas it is actually a state that is entered after the initialization/construction of the value is complete, and exited when the value's memory is later reclaimed.
Apr 07 2020
On Friday, 3 April 2020 at 06:56:27 UTC, FeepingCreature wrote:Maybe D could allow to initialize an immutable variable from the try{} body if the catch{} body is statically known to exit by return/throw?Sounds like making try an expression: immutable struct S { } ... S s = try fun(); // implicit result catch (Exception) return; call(s); The above could also work if the catch block had the result and the try block was a terminating statement. Another option would be a new expression that either yields the first expression if no exception was thrown, or it catches the exception and executes a statement or block that must terminate: S s = fun() __or_catch (Exception e) return; call(s); (I have seen a similar form of expression for unwrapping optional values, terminating if the value doesn't exist in V language). Not sure whether these are worth having yet as I've only just considered them ;-)
Apr 09 2020