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?

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

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.

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.

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.

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.

I mean mutable, of course.

No, it's not. It's not allowed to mutate with inout.
-Steve

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.

inout is *sort of* logical const, if the underlying type is immutable.

I mean mutable, of course.

No, it's not. It's not allowed to mutate with inout.
-Steve

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.

inout does not know when the incoming data is mutable.
Effectively, inout is just like const, but has implications on the return
value.
-Steve

inout does not know when the incoming data is mutable.
Effectively, inout is just like const, but has implications on
the return value.
-Steve

Yeah, 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?

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

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

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?

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.
-Steve

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?

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?

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.
-Steve

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.
-Steve

Which 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.

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.
-Steve

Which 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.

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?

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 Davis

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

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

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...

I hear you. In this case, one might ask why casting away const is
allowed at all :)

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 Davis