digitalmars.D - Best practices for logical const
- Adam D. Ruppe (17/17) Feb 14 2014 D doesn't have logical const, but sometimes it is useful,
- Jonathan M Davis (9/31) Feb 15 2014 If you want logical const, don't use const or immutable at all. To do so...
- Jakob Ovrum (7/21) Feb 15 2014 +1.
- Peter Alexander (6/6) Feb 15 2014 If you want logical const in D, you just don't use const. If you
- Jesse Phillips (14/20) Feb 15 2014 I fear it may not be that simple. Caching tends to be the main
- Timon Gehr (9/11) Feb 15 2014 class C{ bool doit = true; }
- Meta (18/24) Feb 15 2014 inout is *sort of* logical const, if the underlying type is
- Meta (2/4) Feb 15 2014 I mean mutable, of course.
- Steven Schveighoffer (3/6) Feb 15 2014 No, it's not. It's not allowed to mutate with inout.
- Meta (8/17) Feb 15 2014 Right, but inout can accept any of mutable, const, and mutable.
- Steven Schveighoffer (5/21) Feb 15 2014 inout does not know when the incoming data is mutable.
- Meta (18/22) Feb 15 2014 Yeah, but say you magically knew the data was mutable.
- Steven Schveighoffer (5/8) Feb 15 2014 No, because only one version of the function is generated. That is part ...
- Meta (6/15) Feb 15 2014 If you pass a mutable value to a function taking an inout
- Steven Schveighoffer (22/38) Feb 15 2014 I'm not sure what you are asking, because the answer I want to give is s...
- Meta (7/9) Feb 15 2014 Then I don't know what we're arguing about. I'm saying that if
- Steven Schveighoffer (7/15) Feb 15 2014 It is safe if you can guarantee the input is ultimately mutable. But the...
- Meta (28/35) Feb 15 2014 Which is what I said in an earlier post. Wouldn't you be able to
- Steven Schveighoffer (27/62) Feb 15 2014 I think you are making a wrong turn somewhere. This has exactly the same...
- Steven Schveighoffer (5/7) Feb 15 2014 Gahh... should have been:
- Jonathan M Davis (14/25) Feb 15 2014 And that would be pointless. If a parameter is inout, then you have to t...
- Steven Schveighoffer (16/32) Feb 15 2014 As a start, I would take a look at the code that generates the mutex whe...
- Stanislav Blinov (29/29) Feb 15 2014 On Saturday, 15 February 2014 at 04:03:51 UTC, Adam D. Ruppe
- Jonathan M Davis (15/20) Feb 15 2014 You're casting away const if you do that, and that's precisely what you
- Stanislav Blinov (4/13) Feb 15 2014 I hear you. In this case, one might ask why casting away const is
- Jonathan M Davis (10/26) Feb 16 2014 Primarily for cases where you have to pass a const object to a function ...
- Dicebot (4/4) Feb 16 2014 IMHO only practically usable "logical const" alternative in D are
D doesn't have logical const, but sometimes it is useful, especially with lazy initialization of a field, and we can kinda fake it with casts or with global variables. Modifying an immutable object is undefined behavior, so how can we avoid that, and if not, try to minimize the problems in practice? Using global variables to store local state is kinda silly, it seems to me that doing a AA lookup kinda defeats the point of caching in the first place, so I want to focus on the cast method. So: * should we always wrap the write in a synchronized block to minimize the chances that we'll have thread problems with implicitly shared immutable things passed as const? What's the best way to do this btw? * should objects with logical const methods deliberately not provide immutable constructors to prevent them from being immutable? Would this actually work? * Anything else that is likely to come up?
Feb 14 2014
On Saturday, February 15, 2014 04:03:50 Adam D. Ruppe wrote:D doesn't have logical const, but sometimes it is useful, especially with lazy initialization of a field, and we can kinda fake it with casts or with global variables. Modifying an immutable object is undefined behavior, so how can we avoid that, and if not, try to minimize the problems in practice? Using global variables to store local state is kinda silly, it seems to me that doing a AA lookup kinda defeats the point of caching in the first place, so I want to focus on the cast method. So: * should we always wrap the write in a synchronized block to minimize the chances that we'll have thread problems with implicitly shared immutable things passed as const? What's the best way to do this btw? * should objects with logical const methods deliberately not provide immutable constructors to prevent them from being immutable? Would this actually work? * Anything else that is likely to come up?If you want logical const, don't use const or immutable at all. To do so is undefined as they require physical constness/immutability. So, if you want logical const, you need to indicate constness in some other way that's not defined by the language. Pretty much by definition, you can't have logical const with const or immutable, because the only way to even attempt it involves casts, which means undefined behavior. I'd strongly advise against even considering it, let alone trying it. - Jonathan M Davis
Feb 15 2014
On Saturday, 15 February 2014 at 10:52:25 UTC, Jonathan M Davis wrote:If you want logical const, don't use const or immutable at all. To do so is undefined as they require physical constness/immutability. So, if you want logical const, you need to indicate constness in some other way that's not defined by the language. Pretty much by definition, you can't have logical const with const or immutable, because the only way to even attempt it involves casts, which means undefined behavior. I'd strongly advise against even considering it, let alone trying it. - Jonathan M Davis+1. Shoe-horning D's const into C++'s const is a bad idea. They are fundamentally different and shouldn't be used the same way. I like to think D's const exists because of immutable and for that reason alone.
Feb 15 2014
If you want logical const in D, you just don't use const. If you try to hack around it, it will just come back and bite you. As a result, when writing APIs, don't enforce constness of your parameters if you require logical const. e.g. a range will not necessarily have const front and empty. Trying to enforce that will lead to trouble.
Feb 15 2014
On Saturday, 15 February 2014 at 12:23:54 UTC, Peter Alexander wrote:If you want logical const in D, you just don't use const. If you try to hack around it, it will just come back and bite you. As a result, when writing APIs, don't enforce constness of your parameters if you require logical const. e.g. a range will not necessarily have const front and empty. Trying to enforce that will lead to trouble.I fear it may not be that simple. Caching tends to be the main example for wanting logical const, but caching may not be the first thing implemented. Write some code, make what you can const, build from that code, make more things const. Go back and implement caching, remove const from everything. To me, the only requirement for implementing logical const is that you make sure it is never implemented on immutable. Casting away const is not undefined, only mutating it afterwards. But mutating a mutable address is still valid, so as long as _you_ guarantee the address is not immutable, no it won't format your harddrive.
Feb 15 2014
On 02/15/2014 08:34 PM, Jesse Phillips wrote:But mutating a mutable address is still valid, so as long as _you_ guarantee the address is not immutable, no it won't format your harddrive.class C{ bool doit = true; } void foo(const(C) c)pure{ (cast()C).doit = false; } void main(){ auto c = new C; writeln(c.doit); foo(c); if(c.doit) formatHardDrive(); }
Feb 15 2014
On Saturday, 15 February 2014 at 12:23:54 UTC, Peter Alexander wrote:If you want logical const in D, you just don't use const. If you try to hack around it, it will just come back and bite you. As a result, when writing APIs, don't enforce constness of your parameters if you require logical const. e.g. a range will not necessarily have const front and empty. Trying to enforce that will lead to trouble.inout is *sort of* logical const, if the underlying type is immutable. T logicalConst(T)(inout(T) t) { //Error: cannot modify inout expression t t += 1; return t; } void main() { auto n = 0; auto m = logicalConst(m); } The only problem is figuring out if T itself is actually mutable and not immutable or const. If you can do that, then it's fine to cast inout away and mutate the value.
Feb 15 2014
On Saturday, 15 February 2014 at 19:59:07 UTC, Meta wrote:inout is *sort of* logical const, if the underlying type is immutable.I mean mutable, of course.
Feb 15 2014
On Sat, 15 Feb 2014 15:02:44 -0500, Meta <jared771 gmail.com> wrote:On Saturday, 15 February 2014 at 19:59:07 UTC, Meta wrote:No, it's not. It's not allowed to mutate with inout. -Steveinout is *sort of* logical const, if the underlying type is immutable.I mean mutable, of course.
Feb 15 2014
On Sunday, 16 February 2014 at 02:34:36 UTC, Steven Schveighoffer wrote:On Sat, 15 Feb 2014 15:02:44 -0500, Meta <jared771 gmail.com> wrote:Right, but inout can accept any of mutable, const, and mutable. The compiler will statically disallow you from mutating an inout variable, but if you know the underlying data is mutable, it's safe to cast inout away and mutate. If the data is actually mutable, then inout is effectively logical const. I guess the same is true of regular const.On Saturday, 15 February 2014 at 19:59:07 UTC, Meta wrote:No, it's not. It's not allowed to mutate with inout. -Steveinout is *sort of* logical const, if the underlying type is immutable.I mean mutable, of course.
Feb 15 2014
On Sat, 15 Feb 2014 21:36:43 -0500, Meta <jared771 gmail.com> wrote:On Sunday, 16 February 2014 at 02:34:36 UTC, Steven Schveighoffer wrote:inout does not know when the incoming data is mutable. Effectively, inout is just like const, but has implications on the return value. -SteveOn Sat, 15 Feb 2014 15:02:44 -0500, Meta <jared771 gmail.com> wrote:Right, but inout can accept any of mutable, const, and mutable. The compiler will statically disallow you from mutating an inout variable, but if you know the underlying data is mutable, it's safe to cast inout away and mutate. If the data is actually mutable, then inout is effectively logical const. I guess the same is true of regular const.On Saturday, 15 February 2014 at 19:59:07 UTC, Meta wrote:No, it's not. It's not allowed to mutate with inout. -Steveinout is *sort of* logical const, if the underlying type is immutable.I mean mutable, of course.
Feb 15 2014
On Sunday, 16 February 2014 at 02:41:36 UTC, Steven Schveighoffer wrote:inout does not know when the incoming data is mutable. Effectively, inout is just like const, but has implications on the return value. -SteveYeah, but say you magically knew the data was mutable. void logicalConstBump(T)(ref inout(T) t, string originalMutability) { if (originalMutability == "mutable") { cast(T)t += 1; } else { assert(0); } } Obviously this is extremely contrived, but inout is effectively logical const here. Is there no way to know what the "original" mutability of t is at compile-time?
Feb 15 2014
On Sat, 15 Feb 2014 22:04:25 -0500, Meta <jared771 gmail.com> wrote:Obviously this is extremely contrived, but inout is effectively logical const here. Is there no way to know what the "original" mutability of t is at compile-time?No, because only one version of the function is generated. That is part of the benefit of inout over a template. If you DO want to know whether it's mutable, use a template. -Steve
Feb 15 2014
On Sunday, 16 February 2014 at 03:16:16 UTC, Steven Schveighoffer wrote:On Sat, 15 Feb 2014 22:04:25 -0500, Meta <jared771 gmail.com> wrote:If you pass a mutable value to a function taking an inout parameter, what's the difference between that function and an identical function that takes a regular mutable parameter instead of an inout parameter?Obviously this is extremely contrived, but inout is effectively logical const here. Is there no way to know what the "original" mutability of t is at compile-time?No, because only one version of the function is generated. That is part of the benefit of inout over a template. If you DO want to know whether it's mutable, use a template. -Steve
Feb 15 2014
On Sat, 15 Feb 2014 22:17:48 -0500, Meta <jared771 gmail.com> wrote:On Sunday, 16 February 2014 at 03:16:16 UTC, Steven Schveighoffer wrote:I'm not sure what you are asking, because the answer I want to give is so trivial :) Identical functions produce identical code. Const, inout, immutable, whatever, these do not change code generation, just what is allowed to those variables. The rational behind inout came from the strstr problem: const char *strstr(const char *haystack, const char *needle); 1. You want the signature of strstr to guarantee it will not change it's parameters. 2. You do not want strstr to change the CALLER's permissions on the parameters, which it effectively does by applying const to the return value. I want the result of strstr to have the same restrictions as the parameter I pass in (mutable, const or immutable). The only way to do this was with a template. But a template would generate 3 separate versions of strstr with the exact same code in it. Maybe an enterprising compiler can elide the 2 superfluous versions, but there's another problem -- the mutable version would be able to change the parameter! We gave up on item 1. inout does this correctly, and it makes a world of difference in accessors of structs and classes. -SteveOn Sat, 15 Feb 2014 22:04:25 -0500, Meta <jared771 gmail.com> wrote:If you pass a mutable value to a function taking an inout parameter, what's the difference between that function and an identical function that takes a regular mutable parameter instead of an inout parameter?Obviously this is extremely contrived, but inout is effectively logical const here. Is there no way to know what the "original" mutability of t is at compile-time?No, because only one version of the function is generated. That is part of the benefit of inout over a template. If you DO want to know whether it's mutable, use a template. -Steve
Feb 15 2014
On Sunday, 16 February 2014 at 03:26:42 UTC, Steven Schveighoffer wrote:I'm not sure what you are asking, because the answer I want to give is so trivial :)Then I don't know what we're arguing about. I'm saying that if you pass mutable data to an inout function, inout effectively acts as logical const within that function, as the compiler doesn't allow you to modify the inout function argument but it is safe to cast away inout. Do you agree or disagree with this?
Feb 15 2014
On Sat, 15 Feb 2014 23:35:52 -0500, Meta <jared771 gmail.com> wrote:On Sunday, 16 February 2014 at 03:26:42 UTC, Steven Schveighoffer wrote:It is safe if you can guarantee the input is ultimately mutable. But there is no way to enforce such a guarantee. It is on the caller to make sure that is true. I don't see why inout should be identified as any different from const in this respect. You could just as easily say the same thing about const. -SteveI'm not sure what you are asking, because the answer I want to give is so trivial :)Then I don't know what we're arguing about. I'm saying that if you pass mutable data to an inout function, inout effectively acts as logical const within that function, as the compiler doesn't allow you to modify the inout function argument but it is safe to cast away inout. Do you agree or disagree with this?
Feb 15 2014
On Sunday, 16 February 2014 at 04:46:34 UTC, Steven Schveighoffer wrote:It is safe if you can guarantee the input is ultimately mutable. But there is no way to enforce such a guarantee. It is on the caller to make sure that is true. I don't see why inout should be identified as any different from const in this respect. You could just as easily say the same thing about const. -SteveWhich is what I said in an earlier post. Wouldn't you be able to do this with a template somehow? template LogicalConst(T) if (isMutable!T) { alias LogicalConst = inout(T); } void bumpWithConstArg(T)(ref LogicalConst!T t) { //Compiler error if you try to modify t //t += 1; //Fine, cast it to mutable then modify //Nothing unsafe here because we verified t is mutable cast(T)t += 1; } void main() { immutable n = 0; //No IFTI match because n is immutable bumpWithConstArg(n); auto m = 0; //Okay, m is mutable bumpWithConstArg(m); } This doesn't actually work, but this is just off the top of my head.
Feb 15 2014
On Sun, 16 Feb 2014 00:14:07 -0500, Meta <jared771 gmail.com> wrote:On Sunday, 16 February 2014 at 04:46:34 UTC, Steven Schveighoffer wrote:I think you are making a wrong turn somewhere. This has exactly the same behavior: void bumpWithConstArg(T)(ref T t) { t += 1; } void main() { immutable n = 0; bumpWithConstArg(n); // fails to compile auto m = 0; bumpWithConstArg(n); } The problem you have to solve is this: void foo(const(int) t) { bumpWithConstArg(t); } void main() { immutable n = 0; foo(n); // needs to fail auto m = 0; foo(m); // needs to work. } -SteveIt is safe if you can guarantee the input is ultimately mutable. But there is no way to enforce such a guarantee. It is on the caller to make sure that is true. I don't see why inout should be identified as any different from const in this respect. You could just as easily say the same thing about const. -SteveWhich is what I said in an earlier post. Wouldn't you be able to do this with a template somehow? template LogicalConst(T) if (isMutable!T) { alias LogicalConst = inout(T); } void bumpWithConstArg(T)(ref LogicalConst!T t) { //Compiler error if you try to modify t //t += 1; //Fine, cast it to mutable then modify //Nothing unsafe here because we verified t is mutable cast(T)t += 1; } void main() { immutable n = 0; //No IFTI match because n is immutable bumpWithConstArg(n); auto m = 0; //Okay, m is mutable bumpWithConstArg(m); } This doesn't actually work, but this is just off the top of my head.
Feb 15 2014
On Sun, 16 Feb 2014 00:22:33 -0500, Steven Schveighoffer <schveiguy yahoo.com> wrote:The problem you have to solve is this: void foo(const(int) t)Gahh... should have been: void foo(ref const(int) t) -Steve
Feb 15 2014
On Sunday, February 16, 2014 04:35:52 Meta wrote:On Sunday, 16 February 2014 at 03:26:42 UTC, Steven Schveighoffer wrote:And that would be pointless. If a parameter is inout, then you have to treat it as const and cannot possibly know that the argument is mutable unless it's only ever passed a mutable argument and the programmer makes sure that that's all it's ever passed. And if that's the case, what's the point of having it be inout instead of mutable? And you really shouldn't be thinking of inout as being different from const from the function's perspective. The only difference is that if the caller passes a mutable or immutable argument, then they get a return value with the same mutability instead of it ending up as const like it would if the function took const instead of inout. It makes no difference to the function itself, and the function writer should not do anything differently inside the function than they would do if it took const. - Jonathan M DavisI'm not sure what you are asking, because the answer I want to give is so trivial :)Then I don't know what we're arguing about. I'm saying that if you pass mutable data to an inout function, inout effectively acts as logical const within that function, as the compiler doesn't allow you to modify the inout function argument but it is safe to cast away inout. Do you agree or disagree with this?
Feb 15 2014
On Fri, 14 Feb 2014 23:03:50 -0500, Adam D. Ruppe <destructionator gmail.com> wrote:D doesn't have logical const, but sometimes it is useful, especially with lazy initialization of a field, and we can kinda fake it with casts or with global variables. Modifying an immutable object is undefined behavior, so how can we avoid that, and if not, try to minimize the problems in practice? Using global variables to store local state is kinda silly, it seems to me that doing a AA lookup kinda defeats the point of caching in the first place, so I want to focus on the cast method. So: * should we always wrap the write in a synchronized block to minimize the chances that we'll have thread problems with implicitly shared immutable things passed as const? What's the best way to do this btw?As a start, I would take a look at the code that generates the mutex when you synchronize an object. It is the only case where logical const is allowed in D.* should objects with logical const methods deliberately not provide immutable constructors to prevent them from being immutable? Would this actually work?This is a good idea. Can you disable the immutable constructor?* Anything else that is likely to come up?Where you must be very very cautious is immutable pure functions. The compiler may make assumptions that are not correct in terms of shortcuts. My advice is to never change "logical const" data inside a const pure function. Other than that, depending on the use case, use C threading rules when making changes. Basically, if you know the object will never be referenced in multiple threads (very application specific), then you are OK to just change it. Otherwise, I'd put a mutex on it, or (ironically) use the object's monitor to protect it. -Steve
Feb 15 2014
On Saturday, 15 February 2014 at 04:03:51 UTC, Adam D. Ruppe wrote: What about a library solution for something like C++-esque mutable? struct Mutable(T) { private T val; this(T v) { val = v; } property ref T get() const { return *cast(T*)&val; } alias get this; } import std.stdio; struct Foo { private Mutable!int cache; void bar() const { writeln("Updating cache"); ++cache; } property int cached() const { return cache; } } void main() { Foo foo; writeln(foo.cached); foo.bar(); writeln(foo.cached); }
Feb 15 2014
On Sunday, February 16, 2014 03:25:08 Stanislav Blinov wrote:On Saturday, 15 February 2014 at 04:03:51 UTC, Adam D. Ruppe wrote: What about a library solution for something like C++-esque mutable?You're casting away const if you do that, and that's precisely what you shouldn't be doing. const is for _physical_ constness and should never be used if you want logical constness. Even attempting to use const for logical constness is incredibly dangerous, and the compiler is free to change what it does based on the knowledge that an object cannot be changed via a const reference and that an immutable object can never be changed. So, even if you manage to get away with casting away const and mutating in a particular situation right now, there's no guarantee that that code will work across compilers or architectures or that it will continue to work on future versions of the same compiler. The best thing to do is to just forget about even attempting to use const for logical constness. That's not what it's for, and you're going to have bugs (potentially very nasty and subtle bugs) if you attempt it. - Jonathan M Davis
Feb 15 2014
On Sunday, 16 February 2014 at 06:28:45 UTC, Jonathan M Davis wrote:On Sunday, February 16, 2014 03:25:08 Stanislav Blinov wrote:I hear you. In this case, one might ask why casting away const is allowed at all :)On Saturday, 15 February 2014 at 04:03:51 UTC, Adam D. Ruppe wrote: What about a library solution for something like C++-esque mutable?You're casting away const if you do that, and that's precisely what you shouldn't be doing. const is for _physical_ constness and should never be used if you want logical constness...
Feb 15 2014
On Sunday, February 16, 2014 06:55:36 Stanislav Blinov wrote:On Sunday, 16 February 2014 at 06:28:45 UTC, Jonathan M Davis wrote:Primarily for cases where you have to pass a const object to a function which has mutable parameters, and you know that it's not going to mutate its parameters - that and the fact that D is a systems language, so it will let you blow off your foot if you really try. It _is_ possible to mutate a const object without breaking code, but you have to know exactly what you're doing, and it's very, very risky such that it's pretty much always a bad idea. But if you really want to try blowing your foot off, D will let you. It just protects you such that you can't do it without trying (e.g. by casting away const). - Jonathan M DavisOn Sunday, February 16, 2014 03:25:08 Stanislav Blinov wrote:I hear you. In this case, one might ask why casting away const is allowed at all :)On Saturday, 15 February 2014 at 04:03:51 UTC, Adam D. Ruppe wrote: What about a library solution for something like C++-esque mutable?You're casting away const if you do that, and that's precisely what you shouldn't be doing. const is for _physical_ constness and should never be used if you want logical constness...
Feb 16 2014
IMHO only practically usable "logical const" alternative in D are getters (in absence of setters). For all other cases better to simply abandon the concept, it simply does not fit the type system.
Feb 16 2014