digitalmars.D - Const initialization issue, looking for solution
- Jakob Ovrum (43/43) May 29 2013 Consider the following example:
- Dicebot (13/13) May 29 2013 Why something like this is not usable?
- Dicebot (2/2) May 29 2013 P.S. For const reference types we have Rebindable in std.typecons
- Jakob Ovrum (4/6) May 29 2013 I can't imagine how it would work with non-class and
- Jakob Ovrum (3/16) May 29 2013 const(int) i = tmp; // fails when the type has mutable
- Dicebot (4/6) May 29 2013 Ah, I understand that know. Well, until flow analysis is here I
- Jakob Ovrum (6/9) May 29 2013 I'm hoping there's a way to restructure the code so it works
- deadalnix (5/21) May 29 2013 I guess the first assignment of a variable should be considered
- Jakob Ovrum (11/15) May 29 2013 Yeah, and languages like C# solve these problems quite
- Andrei Alexandrescu (4/18) May 29 2013 I must have missed part of this - how would the desired setup work?
- Jakob Ovrum (40/43) May 29 2013 Maybe something like the following would work. collectException's
- Jakob Ovrum (6/7) May 29 2013 I'm failing to find this really good blog post by a C# designer I
- Jakob Ovrum (7/8) May 29 2013 I can't find the blog posts, but I think it's basically just a
- =?UTF-8?B?QWxpIMOHZWhyZWxp?= (31/32) May 29 2013 I have looked only your short example. std.exception.ifThrown may work:
- Kenji Hara (18/34) May 29 2013 With 2.063, this code would work.
- Jakob Ovrum (6/24) May 29 2013 Hm, well the problem isn't to make something mutable into
- Steven Schveighoffer (6/21) May 29 2013 Everything mutable and immutable (and const, or inout) is implicitly
- Maxim Fomin (18/25) May 29 2013 Fundamental issue here is that
- Jakob Ovrum (6/23) May 29 2013 It's generic code. See the linked code and you'll see that this
- Maxim Fomin (4/23) May 29 2013 It is not casting away const and it has nothing to do with
- Jakob Ovrum (9/12) May 29 2013 The data is not initially constructed by the local function, it's
- Maxim Fomin (2/15) May 29 2013 If you accept const object as a parameter, then yes.
- Jonathan M Davis (17/43) May 29 2013 Wrap the try-catch in a function.
- Steven Schveighoffer (4/5) May 29 2013 I was about to suggest this. This is the correct solution.
- Jonathan M Davis (10/21) May 29 2013 Ouch, my e-mail client ate the indentation. That should be
- Jakob Ovrum (5/5) May 30 2013 Thanks for the response, everyone.
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
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
P.S. For const reference types we have Rebindable in std.typecons AFAIR
May 29 2013
On Wednesday, 29 May 2013 at 12:42:11 UTC, Dicebot wrote:P.S. For const reference types we have Rebindable in std.typecons AFAIRI 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
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
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
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
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: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.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
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
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
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? AndreiMaybe 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
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
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
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
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
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 HaraHm, 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
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: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? -SteveWhy 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
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
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
On Wednesday, 29 May 2013 at 13:30:09 UTC, Jakob Ovrum 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.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
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
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:If you accept const object as a parameter, then yes.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
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
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
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
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