www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - How to initialize immutable variables with an expression that throws

reply FeepingCreature <feepingcreature gmail.com> writes:
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
next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
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
parent reply FeepingCreature <feepingcreature gmail.com> writes:
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
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 4/3/20 9:14 AM, FeepingCreature wrote:
 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.
totally misread your statement as making s immutable, not S. I don't know how to solve that problem correctly. -Steve
Apr 03
prev sibling next sibling parent reply Atila Neves <atila.neves gmail.com> writes:
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
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 4/3/20 9:21 AM, Atila Neves wrote:
 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(); } --------------------
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. -Steve
Apr 03
prev sibling next sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
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
parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Friday, 3 April 2020 at 15:43:51 UTC, Ali Çehreli wrote:
 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)): [snip]
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?
Apr 07
parent reply tsbockman <thomas.bockman gmail.com> writes:
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
next sibling parent Max Samukha <maxsamukha gmail.com> writes:
On Tuesday, 7 April 2020 at 19:52:36 UTC, tsbockman wrote:
 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.
Exactly (with a small correction that the state is exited before the destruction if there is any)!
Apr 07
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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
prev sibling parent FeepingCreature <feepingcreature gmail.com> writes:
On Tuesday, 7 April 2020 at 19:52:36 UTC, tsbockman wrote:
 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.
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.
Apr 07
prev sibling parent Nick Treleaven <nick geany.org> writes:
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