12

I'm a C++ senior programmer. I'm currently doing some Go programming. The only feature I really miss is the const qualifier. In go, if you want to modify an object, you pass its pointer. If you don't want to modify it, you pass it by value. But if the struct is big, you should pass it by pointer, which overrides the no-modification feature. Worse, you can pass an object by value, but if it contains a pointer, you can actually modify its contents, with terrible race condition dangers. Some language types like maps and slices have this feature. This happens in a language that's supposed to be built for concurrency. So the issue of avoiding modification is really non-existent in Go, and you should pass small objects that do not contain pointers (you must be aware that the object does not contain a pointer) by value, if they aren't gonna be modified.

With const, you can pass objects by const pointer and don't worrying about modification. Type-safety is about having a contract that allows speed and prevents type-related bugs. Another feature that does this too is the const qualifier.

chila
  • 2,372
  • 1
  • 16
  • 33
  • 7
    Note that you can (and IMHO should) address this with interfaces; you can define an interface that exposes an immutable view of your data structure. Re: "Worse, you can pass an object by value, but if it contains a pointer, you can actually modify its contents": The same is true in C++; note that `const` is *not* transitively inherited by pointer members. – ruakh Nov 14 '15 at 19:20
  • 3
    The same reasons that were given for C#: [why there is no const member method in c# and const parameter](http://stackoverflow.com/questions/4150478/why-there-is-no-const-member-method-in-c-sharp-and-const-parameter). – peterSO Nov 14 '15 at 20:19
  • @ruakh But in C++, you can prevent the object from being copied, or when copied, apply the desired semantics (ref-counting, deep copy, etc). The interface technique is valid thou. You can/should pass a Reader interface instead of a *File for example. Thanks. – chila Nov 15 '15 at 04:08
  • 1
    @peterSO The answer by the Eric dude is terrible. It's not about guarantee, it's about contracts. You can const-cast an object inside a function and modify it, the same way you can put a if random % 1000000 == 0 { panic() } inside a Go function. With the read-only interface technique, you can cast to the original object and modify it. _You should not do either ones_. – chila Nov 15 '15 at 04:16
  • @chila: Now you are making this about opinions so the question should be closed. – peterSO Nov 15 '15 at 04:37
  • @peterSO When someone answers a simple question in a rude, vague and misleading way, like Eric's answer, I will point that out. He's the one being opinionated actually. If another technique like the one ruakh gave me allows me to cope with the lacking of a feature, that's enough since doing something with less is better than doing something with more. – chila Nov 15 '15 at 14:22
  • 1
    From at least [one of the developers](https://groups.google.com/forum/#!msg/golang-nuts/_XiGWtxhmhE/8iWxTYXCfn0J) it would be "While there may be some benefit to marking objects immutable in some way, we don't think a const type qualifier is to way to go." – nos Nov 15 '15 at 22:52

2 Answers2

6

The const type qualifier in C/C++ has various meanings. When applied to a variable, it means that the variable is immutable. That's a useful feature, and one that is missing from Go, but it's not the one you seem to be talking about.

You are talking about the way that const can be used as a partially enforced contract for a function. A function can give a pointer parameter the const qualifier to mean that the function won't change any values using that pointer. (Unless, of course, the function uses a cast (a const_cast in C++). Or, in C++, the pointer points to a field that is declared mutable.)

Go has a very simple type system. Many languages have a complex type system in which you enforce the correctness of your program by writing types. In many cases this means that a good deal of programming involves writing type declarations. Go takes a different approach: most of your programming involves writing code, not types. You write correct code by writing correct code, not by writing types that catch cases where you write incorrect code. If you want to catch incorrect code, you write analyzers, like go vet that look for cases that are invalid in your code. These kinds of analyzers are much much easier to write for Go than for C/C++, because the language is simpler.

There are advantages and disadvantages to this kind of approach. Go is making a clear choice here: write code, not types. It's not the right choice for everyone.

  • Const in C++ is always about a an immutable value or a pointer to a immutable one. I don't think Go is about "you write correct code by writing correct code", it's mostly about "don't add a feature unless it's ultra necessary". With immutable interfaces and wrappers, it's seems not to be for const. – chila Nov 17 '15 at 19:15
2

Please treat it as an expanded comment. I'm not any programming language designer, so can't go deep inside the details here, but will present my opinion as a long-term developer in C++ and short-term developer in Go.

Const is a non-trivial feature for the compiler, so one would have to make sure whether it's providing enough advantage for the user to implement it as well as won't sacrifice the simplicity of syntax. You might think it's just a const qualifier we're talking about, but looking at C++ itself, it's not so easy – there're a lot of caveats.

  1. You say const is a contract and you shouldn't be able to modify it at any circumstances. One of your arguments against using read only interfaces is that you can cast it to original type and do whatever you want. Sure you can. The same way you can show a middle finger to the contract in C++ by using const_cast. For some reason it was added to the language and, not sure I should be proud of it, I've used it once or twice.

  2. There's another modifier in C++ allowing you to relax the contract – mutable. Someone realised that const structures might actually need to have some fields modified, usually mutexes protecting internal variables. I guess you would need something similar in Go in order to be able to implement thread-safe structures.

  3. When it comes simple const int x people can easily follow. But then pointers jump in and people really get consfused. const int * x, int * const x, const int * const x – these are all valid declarations of x, each with different contract. I know it's not a rocket science to choose the right one, but does your experience as a senior C++ programmer tell you people widely understand these and are always using the right one? And I haven't even mentioned things like const int * const * * * const * const x. It blows my mind.

Before I move to point 4, I would like to cite the following:

Worse, you can pass an object by value, but if it contains a pointer, you can actually modify its contents

Now this is interesting accusation. There's the same issue in C++; worse – it exists even if you declare object as const, which means you can't solve the problem with a simple const qualifier. See the next point:

  1. Per 3, and pointers, it's not so easy to express the very right contract and things sometimes get unexpected. This piece of code surprised a few people:

    struct S {
        int *x;
    };
    
    int main() {
        int n = 7;
        const S s = {&n}; // don't touch s, it's read only!
        *s.x = 666; // wait, what? s is const! is satan involved?
    }
    

    I'm sure it's natural for you why the code above compiles. It's the pointer value you can't modify (the address it points to), not the value behind it. You must admit there're people around that would raise their eyebrow.

I don't know if it makes any point, but I've been using const in C++ all the time. Very accurate. Going mental about it. Not sure whether is has ever saved my ass, but after moving to Go I must admit I've never missed it. And having in mind all these edge cases and exceptions I can really believe creators of a minimalistic language like Go would decide to skip on this one.

Type-safety is about having a contract that allows speed and prevents type-related bugs.

Agreed. For example, in Go, I love there're no implicit conversions between types. This is really preventing me from type-related bugs.

Another feature that does this too is the const qualifier.

Per my whole answer – I don't agree. Where a general const contract would do this for sure, a simple const qualifier is not enough. You then need a mutable one, maybe kind of a const_cast feature and still – it can leave you with misleading believes of protection, because it's hard to understand what exactly is constant.

Hopefully some language creators will design a perfect way of defining constants all over in our code and then we'll see it in Go. Or move over to the new language. But personally, I don't think C++'s way is a particularly good one.

(Alternative would be to follow functional programming paradigms, which would love to see all their "variables" immutable.)

tomasz
  • 12,574
  • 4
  • 43
  • 54
  • 2
    Right, so this is what I called an _expanded comment_. – tomasz Nov 15 '15 at 22:31
  • 1
    1. I'm not against immutable interfaces. I'm against the rationale that const is worthless because your can circumvent it. 2. Mutable gives you an exception to the contract for situations where mutability doesn't affect the state of a const object in a meaningful way (like a mutex). 3. You don't write code like that in well written programs. You might use typedefs and stuff like that, to clarify. 3.5. You can solve the inner pointer problem at the constructor, if one should exist. 4. The structure 'S' needs a constructor. – chila Nov 16 '15 at 02:32
  • 1
    C++'s const is perfect and I never had any issues with it. But I realize for it to be effective, you might need other stuff like constructors. For now, the immutable interface is the way to go. – chila Nov 16 '15 at 02:33