www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Const initialization issue, looking for solution

reply "Jakob Ovrum" <jakobovrum gmail.com> writes:
Consider the following example:

http://dpaste.dzfl.pl/a0595ddf
----
// Can throw, and we want to catch
int createTheVar();

// Can also throw, but we don't want to catch it here
int transform(int a);

int foo()
{
	const(int) i;

	try
	{
		i = createTheVar(); // Clearly not allowed
	}
	catch(Exception e) // For demo purposes
	{
		// Exception handling code
	}

	return transform(i);
}
----
I need to be able to catch an exception that may have been thrown 
during the initialization of `i`, but still have `i` be const. I 
can't omit the variable because I *don't* want to accidentally 
catch anything that transform() may have thrown.

Note that the simple type const(int) is an example. This is 
actually in highly generic code and the type may have mutable 
indirection.

Here's the real-world code:
https://github.com/JakobOvrum/LuaD/blob/master/luad/conversions/functions.d#L86

The particular problematic type I happened to encounter for 
RetType is the const(CacheInfo)[5] return type of 
core.cpuid.dataCaches:


Specifics aside, I believe this is a very general problem.

----
Of course, if D had well-defined, local flow analysis like many 

rejected. I think that's a discussion for the future though; 
right now it would be nice to have a workable, general 
solution/workaround to the issue of initializing const/immutable 
variables in try-blocks.

Any help is much appreciated.
May 29 2013
next sibling parent reply "Dicebot" <m.strashun gmail.com> writes:
Why something like this is not usable?
-----------------------
int tmp;
try
{
    tmp = ...;
}
catch(..)
{
}
const(int) i = tmp;
-----------------------
Not really pretty but nothing crazy.
May 29 2013
next sibling parent reply "Dicebot" <m.strashun gmail.com> writes:
P.S. For const reference types we have Rebindable in std.typecons 
AFAIR
May 29 2013
parent "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Wednesday, 29 May 2013 at 12:42:11 UTC, Dicebot wrote:
 P.S. For const reference types we have Rebindable in 
 std.typecons AFAIR
I can't imagine how it would work with non-class and non-interface references, like references embedded in const struct instances or const static arrays of such structs.
May 29 2013
prev sibling parent reply "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Wednesday, 29 May 2013 at 12:40:39 UTC, Dicebot wrote:
 Why something like this is not usable?
 -----------------------
 int tmp;
 try
 {
    tmp = ...;
 }
 catch(..)
 {
 }
 const(int) i = tmp;
 -----------------------
 Not really pretty but nothing crazy.
const(int) i = tmp; // fails when the type has mutable indirection.
May 29 2013
next sibling parent reply "Dicebot" <m.strashun gmail.com> writes:
On Wednesday, 29 May 2013 at 12:43:46 UTC, Jakob Ovrum wrote:
 const(int) i = tmp; // fails when the type has mutable 
 indirection.
Ah, I understand that know. Well, until flow analysis is here I am afraid you can't really do anything better than assumeUnique or similar utility function.
May 29 2013
parent "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Wednesday, 29 May 2013 at 12:54:24 UTC, Dicebot wrote:
 Ah, I understand that know. Well, until flow analysis is here I 
 am afraid you can't really do anything better than assumeUnique 
 or similar utility function.
I'm hoping there's a way to restructure the code so it works (making it more functional or whatever), but I can't for the life of me think of a way :< Maybe leveraging another function, hopefully a Phobos function (like std.exception ones), can solve the issue neatly...
May 29 2013
prev sibling next sibling parent reply "deadalnix" <deadalnix gmail.com> writes:
On Wednesday, 29 May 2013 at 12:43:46 UTC, Jakob Ovrum wrote:
 On Wednesday, 29 May 2013 at 12:40:39 UTC, Dicebot wrote:
 Why something like this is not usable?
 -----------------------
 int tmp;
 try
 {
   tmp = ...;
 }
 catch(..)
 {
 }
 const(int) i = tmp;
 -----------------------
 Not really pretty but nothing crazy.
const(int) i = tmp; // fails when the type has mutable indirection.
I guess the first assignment of a variable should be considered as a declaration unless it has been read in between. This is the same problem as immutable constructor, and also relate to extra copies elimination.
May 29 2013
parent reply "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Wednesday, 29 May 2013 at 13:19:34 UTC, deadalnix wrote:
 I guess the first assignment of a variable should be considered 
 as a declaration unless it has been read in between. This is 
 the same problem as immutable constructor, and also relate to 
 extra copies elimination.
beautifully. I don't think there's a reason we can't do almost the same thing in D, except it requires implementation effort and overcoming Walter's protests. Regardless, it's probably not something to expect in the short-term. ---- I was unable to leverage std.exception. collectException looks promising at first glance - and maybe if the return value and out parameter were switched, it would work - but then I suspect the function's implementation would face exactly the same problem.
May 29 2013
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 5/29/13 9:25 AM, Jakob Ovrum wrote:
 On Wednesday, 29 May 2013 at 13:19:34 UTC, deadalnix wrote:
 I guess the first assignment of a variable should be considered as a
 declaration unless it has been read in between. This is the same
 problem as immutable constructor, and also relate to extra copies
 elimination.
don't think there's a reason we can't do almost the same thing in D, except it requires implementation effort and overcoming Walter's protests. Regardless, it's probably not something to expect in the short-term.
 I was unable to leverage std.exception. collectException looks promising
 at first glance - and maybe if the return value and out parameter were
 switched, it would work - but then I suspect the function's
 implementation would face exactly the same problem.
I must have missed part of this - how would the desired setup work? Andrei
May 29 2013
next sibling parent "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Wednesday, 29 May 2013 at 15:11:53 UTC, Andrei Alexandrescu 
wrote:
 I must have missed part of this - how would the desired setup 
 work?


 Andrei
Maybe something like the following would work. collectException's return value and out parameter have been swapped for demonstration - an out parameter for the result just moves the problem to the body of collectException: ---- // Can throw, and we want to catch int createTheVar(); // Can also throw, but we don't want to catch it here int transform(int a); int foo() { Exception e; const(int) i = collectException(createTheVar(), e); if(e) // Exception handling code return transform(i); } ---- By removing the scope created by try-catch, the variable can be declared and initialized at the same time, satisfying the requirements of const/immutable. Further, the transform() call is left unguarded as specified. Such a collectionException works because it can use `return` inside the try scope without broadening what it catches: ---- E collectException(T = Exception, E)(lazy E exp, out T ex) { try return exp(); catch(T e) { ex = e; return E.init; // Kind of a drawback } } ---- So, I guess using another function to do the try-catch is a workable solution.
May 29 2013
prev sibling next sibling parent "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Wednesday, 29 May 2013 at 15:11:53 UTC, Andrei Alexandrescu 
wrote:

read a while ago that explains how it works. I'll keep looking; my memory is fuzzy on the details and I wouldn't be able to explain it well.
May 29 2013
prev sibling parent "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Wednesday, 29 May 2013 at 15:11:53 UTC, Andrei Alexandrescu 
wrote:

I can't find the blog posts, but I think it's basically just a very good implementation of definite assignment analysis[1], without any inter-procedural analysis. So, nothing spectacular (though I think it's really nice). [1] https://en.wikipedia.org/wiki/Definite_assignment_analysis
May 29 2013
prev sibling parent =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 05/29/2013 06:25 AM, Jakob Ovrum wrote:

 I was unable to leverage std.exception.
I have looked only your short example. std.exception.ifThrown may work: import std.typecons; import std.exception; alias ToThrow = Flag!"ToThrow"; // Can throw, and we want to catch int createTheVar(ToThrow toThrow) { if (toThrow == ToThrow.yes) { throw new Exception("bad cat!"); } return 42; } // Can also throw, but we don't want to catch it here int transform(int a) { return a + 1; } int foo(ToThrow toThrow) { const(int) i = createTheVar(toThrow).ifThrown!Exception(666); return transform(i); } unittest { assert(foo(ToThrow.yes) == 667); assert(foo(ToThrow.no) == 43); } void main() {} Ali
May 29 2013
prev sibling next sibling parent reply Kenji Hara <k.hara.pg gmail.com> writes:
With 2.063, this code would work.

struct S { int* ptr; }  // has mutable indirection

void main()
{
    immutable S si = function () pure
    {
        S sm;
        sm.ptr = new int;
        *sm.ptr = 10;
        return sm;  // construct and return mutable object
    }();
    static assert(is(typeof(*si.ptr) == immutable int));
    assert(*si.ptr == 10);
}

The local function would return an unique object, so it is implicitly
convertible to immutable.

Kenji Hara


2013/5/29 Jakob Ovrum <jakobovrum gmail.com>

 On Wednesday, 29 May 2013 at 12:40:39 UTC, Dicebot wrote:

 Why something like this is not usable?
 -----------------------
 int tmp;
 try
 {
    tmp = ...;
 }
 catch(..)
 {
 }
 const(int) i = tmp;
 -----------------------
 Not really pretty but nothing crazy.
const(int) i = tmp; // fails when the type has mutable indirection.
May 29 2013
parent "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Wednesday, 29 May 2013 at 15:14:54 UTC, Kenji Hara wrote:
 With 2.063, this code would work.

 struct S { int* ptr; }  // has mutable indirection

 void main()
 {
     immutable S si = function () pure
     {
         S sm;
         sm.ptr = new int;
         *sm.ptr = 10;
         return sm;  // construct and return mutable object
     }();
     static assert(is(typeof(*si.ptr) == immutable int));
     assert(*si.ptr == 10);
 }

 The local function would return an unique object, so it is 
 implicitly
 convertible to immutable.

 Kenji Hara
Hm, well the problem isn't to make something mutable into immutable, so the purity and implicit conversion is not necessary, but using a nested or anonymous function would indeed be a good way to move the problematic try-catch into another function.
May 29 2013
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 29 May 2013 08:43:44 -0400, Jakob Ovrum <jakobovrum gmail.com>  
wrote:

 On Wednesday, 29 May 2013 at 12:40:39 UTC, Dicebot wrote:
 Why something like this is not usable?
 -----------------------
 int tmp;
 try
 {
    tmp = ...;
 }
 catch(..)
 {
 }
 const(int) i = tmp;
 -----------------------
 Not really pretty but nothing crazy.
const(int) i = tmp; // fails when the type has mutable indirection.
Everything mutable and immutable (and const, or inout) is implicitly convertible to const. The above should work for any type of tmp. Can you cite specific code that is failing? -Steve
May 29 2013
prev sibling next sibling parent reply "Maxim Fomin" <maxim maxim-fomin.ru> writes:
On Wednesday, 29 May 2013 at 12:36:19 UTC, Jakob Ovrum wrote:
 I need to be able to catch an exception that may have been 
 thrown during the initialization of `i`, but still have `i` be 
 const. I can't omit the variable because I *don't* want to 
 accidentally catch anything that transform() may have thrown.

 Note that the simple type const(int) is an example. This is 
 actually in highly generic code and the type may have mutable 
 indirection.
Fundamental issue here is that const T t; is almost useless, it is essentially immutable since you cannot change it and you cannot alias it. As such, it probably should be changed to a mutable object or an immutable one. However, since const/mutable aliasing is allowed, you can do: union U (T) { const T ct; T mt; } because const doesn't mean that object would never change, you can mutate it through alias. From the opposite view, there is also no problem in reinterpeting a mutable object as const object (since mutables can be converted to const). Depending on your mutable indirection situation this can work or may not.
May 29 2013
parent reply "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Wednesday, 29 May 2013 at 13:21:21 UTC, Maxim Fomin wrote:
 Fundamental issue here is that

 const T t;

 is almost useless, it is essentially immutable since you cannot 
 change it and you cannot alias it. As such, it probably should 
 be changed to a mutable object or an immutable one.
It's generic code. See the linked code and you'll see that this is not an option.
 However, since const/mutable aliasing is allowed, you can do:

 union U (T)
 {
    const T ct;
    T mt;
 }

 because const doesn't mean that object would never change, you 
 can mutate it through alias. From the opposite view, there is 
 also no problem in reinterpeting a mutable object as const 
 object (since mutables can be converted to const).

 Depending on your mutable indirection situation this can work 
 or may not.
Mutable indirection is actually not the problem with this solution, it's the casting away of const. There's no guarantee here that the data wasn't actually immutable when it was created.
May 29 2013
parent reply "Maxim Fomin" <maxim maxim-fomin.ru> writes:
On Wednesday, 29 May 2013 at 13:30:09 UTC, Jakob Ovrum wrote:
 However, since const/mutable aliasing is allowed, you can do:

 union U (T)
 {
   const T ct;
   T mt;
 }

 because const doesn't mean that object would never change, you 
 can mutate it through alias. From the opposite view, there is 
 also no problem in reinterpeting a mutable object as const 
 object (since mutables can be converted to const).

 Depending on your mutable indirection situation this can work 
 or may not.
Mutable indirection is actually not the problem with this solution, it's the casting away of const. There's no guarantee here that the data wasn't actually immutable when it was created.
It is not casting away const and it has nothing to do with immutable, also this does not suffer from data was actually immutable problem.
May 29 2013
parent reply "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Wednesday, 29 May 2013 at 13:35:18 UTC, Maxim Fomin wrote:
 It is not casting away const and it has nothing to do with 
 immutable, also this does not suffer from data was actually 
 immutable problem.
The data is not initially constructed by the local function, it's clearly coming from somewhere else and there's no guarantee that it's not returning a const view of immutable data. The code is not limited to accepting const, of course, and exhibits the same problem when the function returns immutable data. Unions are a convenience feature, it boils down to the same as a cast and has the same problems (actually worse because the conversion is not explicit).
May 29 2013
parent "Maxim Fomin" <maxim maxim-fomin.ru> writes:
On Wednesday, 29 May 2013 at 13:44:15 UTC, Jakob Ovrum wrote:
 On Wednesday, 29 May 2013 at 13:35:18 UTC, Maxim Fomin wrote:
 It is not casting away const and it has nothing to do with 
 immutable, also this does not suffer from data was actually 
 immutable problem.
The data is not initially constructed by the local function, it's clearly coming from somewhere else and there's no guarantee that it's not returning a const view of immutable data. The code is not limited to accepting const, of course, and exhibits the same problem when the function returns immutable data. Unions are a convenience feature, it boils down to the same as a cast and has the same problems (actually worse because the conversion is not explicit).
If you accept const object as a parameter, then yes.
May 29 2013
prev sibling next sibling parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Wednesday, May 29, 2013 14:36:18 Jakob Ovrum wrote:
 Consider the following example:
 
 http://dpaste.dzfl.pl/a0595ddf
 ----
 // Can throw, and we want to catch
 int createTheVar();
 
 // Can also throw, but we don't want to catch it here
 int transform(int a);
 
 int foo()
 {
 const(int) i;
 
 try
 {
 i = createTheVar(); // Clearly not allowed
 }
 catch(Exception e) // For demo purposes
 {
 // Exception handling code
 }
 
 return transform(i);
 }
 ----
Wrap the try-catch in a function. int foo() { int initI() { try return createTheVar(); catch(Exception e) return int.init; } const int i = initI(); return transform(i); } And as it's a nested function, you can even have access to foo's scope (or make it static if you don't need that). - Jonathan M Davis
May 29 2013
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 29 May 2013 15:08:24 -0400, Jonathan M Davis <jmdavisProg gmx.com>  
wrote:

 Wrap the try-catch in a function.
I was about to suggest this. This is the correct solution. -Steve
May 29 2013
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Wednesday, May 29, 2013 15:08:24 Jonathan M Davis wrote:
 Wrap the try-catch in a function.
 
 int foo()
 {
 int initI()
 {
 try
 return createTheVar();
 catch(Exception e)
 return int.init;
 }
Ouch, my e-mail client ate the indentation. That should be    int initI()    {        try            return createTheVar();        catch(Exception e)            return int.init;    } - Jonathan M Davis
May 29 2013
prev sibling parent "Jakob Ovrum" <jakobovrum gmail.com> writes:
Thanks for the response, everyone.

Unfortunately, it seems until we have some kind of flow analysis, 
using another function is the only solution after all. At least 
we have the option of expressing it using a standard library 
function, ifThrown.
May 30 2013