Transitive const sucks

Janice Caron caron800 at googlemail.com
Wed Sep 12 14:22:30 PDT 2007


On 9/12/07, Walter Bright <newshound1 at digitalmars.com> wrote:

> Because multithreaded programming doesn't start working if the interface
> hides the changing variables. If it did, parallel programming would be
> easy. Logical constness is *still* changing state, and since it is, all
> the boogeymen of synchronization, race conditions, deadlocks, etc., are
> all there.

I've written a lot of multithreaded programming. A lot.

...which is perhaps a bad thing, because sometimes I forget that D
does things differently. I don't need to use a separate mutex class if
you've got synchronized.

(By the way - could you also allow the spelling "synchronised" for the
benefit of speakers of British English?) :-)

synchronised is a blunt tool though. In my C++ code, I arrange it so
that multiple threads can simultaneously obtain read-access, or
exactly one thread can obtain write-access. Correct me if I'm wrong,
but the built-in synchronized feature isn't that smart?

So anyway, as I mentioned further up this thread, I had this loopup
class member function that got stuff from a file and cached it. I
assure you it was thread-safe. It even allowed multiple readers (if
the key/value pair was already cached), but if a lookup was not
cached, one thread and one thread only got to open the file, read a
chunk of data, and stick it in the file. This was a class that knew
what it was doing.

You're probably going to tell me that I can still do all that in D,
providing I don't declare it const. The problem is, if I don't declare
it const, /and/ all consts in D are transitive, then I can't store a
reference to that object in any const structure. Basically I'd end up
declaring just about nothing const, and I'd see no benefit.


> Logical constness hides the state change from the awareness of the
> programmer, but it's still changing state.

By definition, yes.


> Being in a module doesn't prevent another thread from using the module's
> interface and thereby changing the state.

Is that why you object?

I would have thought it the programmer's problem to ensure thread
safety, not the compiler's. If I have a variable that's going to be
accessed by multiple threads, then I'm going to make sure it's
properly synchronised, and I if screw up, then it's my bug.



> Yes, but that requires programmer discipline, which is unreliable and
> scales poorly.

I think it could scale very well with the right language support. But
that's another subject.


> Very few programmers are able to successfully write such
> code. With FP programming, most programmers will be able to.

That's obviously true. I'm not sure why it's relevant though. You said
you had a "pure" keyword in mind for that. But if I don't use the pure
keyword, then thread safety should be down to me, right?


> It has come up before. The answer is to not declare logically const
> things as being const, because they aren't const. Logical constness
> belongs as a comment because it is not compiler checkable.

But you might want to declare logically const things as being const
for efficiency reasons. And to argue that "they aren't const" is a
matter of definition. "const" in C++ is /defined/ to mean logical
const, so const they are! You use the word to mean "physically const".
We don't all agree on the definition.

Suppose there exists a class String with member function uppercase(),
declared const (or invariant, as you would have it in D). So far, so
good.

But then, along comes a better (or at least, different) string class
with the same interface, and it happens to be faster, and I want to
use the new FastString class in place of the old String class. All I
have to do is search and replace, right? Or just make an alias. But
there's a problem. Turns out, the reason it's faster is because it
caches some results internally so it doesn't have to keep recomputing
them.

>From the point of view of "implementation should be independent of
interface" this internal detail is something I shouldn't need to know
or care about.

But now suddenly, I do need to know about it. And care. Because now
that uppercase() function I mentioned earlier is no longer physically
const. That means, if I use it as a drop-in replacement, my code now
won't compile.



> I'll reiterate that const-that-isn't-checkably-constant has no value
> beyond being a comment, so it might as well just be a comment.

Oh contraire. It /is/ meaningful. It is a cast iron guarantee that no
non mutable (which I argue should imply non-private) variables will be
modified. Without the const declaration, you do not have that
guarantee. It's a guarantee *which the compiler can use* to help the
programmer catch errors! For example, this C++

class C
{
    mutable int x;
    int y;

    void f() const;
    {
        x = 1;
        y = 2; /* The compiler catches this error */
     }
}

See - this is useful to me. If I had not declared f const (which is
basically what you're suggesting), then the compiler would have been
no help to me whatsoever. It would not have flagged y=2 as an error.

I /want/ the compiler to assist me by flagging errors like this. I
want it to be a compile-time error for f to modify y. But as soon as
remove the words "const" and "mutable" from that example, I lose that
compiler-assistance.



> The D compiler does not necessarily know all there is to know about a type:

OK. Good point. I hadn't thought that one through.

The difficulty is, if you don't allow mutable member variables, then
we're back to what started this thread - transitivity sucks.

See, I was basically arguing with you, that transitivity is a good
thing -- /provided/ we can have mutable members. Now, if we can't have
that, then suddenly transitivity no longer seems so welcoming. If
const-transitivity becomes a strait-jacket, then folk are going to end
up just not declaring anything const at all, because they can't get
their code to compile otherwise.



More information about the Digitalmars-d mailing list