Thursday, March 18, 2010

To const or not to const?

I am a C expert. I've put in my ten-plus years with it, and by now my understanding of the language is not limited merely to knowing keywords, the standard library, and language-use conventions; I see C programs as complex and abstract patterns. When I code in C, I do not translate thoughts to C; I think in C. I would call myself a master of the language if only my experience had more breadth. Even so, I've used C in academia, personal, and corporate settings; I've done projects as a lackey and projects as a lead; I know C99; I've used many different compilers on many different operating systems; recently I've done work on a small embedded system with no operating system; so even the breadth of my experience is good if it isn't great.

That said, let it be known that I struggle with one of the C language's simplest constructs, the const keyword.

Sure, I know the const keyword. I know its syntax and meaning. I know some of its peculiarities, such as that the declaration char const * * const foo declares foo to be a mutable array of immutable C strings and that foo itself is immutable. I know that the const keyword trips up a lot of intermediate-level C developers when it's mixed with pointers. I'm not one of those people.

Like I said, I see C programs as patterns, and here lies my problem with the const keyword; I don't understand how it best fits into a program's pattern. Here's an example.
struct foo;
typedef struct foo foo_t;

foo_t *foo_copy(foo_t const *src);

This is an example of a function, foo_copy, that takes an object of type foo_t and creates and returns a copy of the object. The question is: should the function argument be of type foo_t const * or foo_t *? To const or not to const?

In most cases, adding the const keyword provides a pleasant safeguard, both for the function foo_copy itself as well as its callers (and their callers and so on). Calling functions can be assured that the state of the object pointed to by src will not change. And we expect that its state won't change.

But there is at least one case in which the object's state should change, and that is when the object is not actually copied but is instead reference-copied. Take the following implementation of foo_copy.
foo_t *foo_copy(foo_t const *src)
{
assert(src != NULL);
src->ref_cnt++;
return src;
}

This will generate a compiler error, though the spirit of the function is correct. The developer then has two options: one, to remove the const keyword from the argument specification and two, to add a const-removing cast to the offending line like so: ((foo_t *) src)->ref_cnt++. The first option potentially breaks existing code that expects the function's const-ness. The second option is impure and, I would argue, more dangerous than goto, both in principal and in practice.

This example exposes a fundamental problem with the const keyword, which is that the keyword breaks the barrier between interface and implementation. Const poses as a keyword that denotes interface, but it is actually a keyword that specifies, in part, implementation. It transforms, however slightly, a black box into a gray box.

I want to discard the const keyword and avoid it in my function prototypes, but the problem with that is that const does have its value. Like assert, it cannot guard against all faults but it can catch some of them, and it does so without increasing size or hindering performance. But languages such as Java do not have const. Historically, I have believed this to be a deficiency in those languages, but now I wonder whether those languages' designers well understood const's deficiencies and saw the construct as a dangerous (though well meant) crutch and decided to take that crutch away from their developers.

Only one thing about the matter is certain to me: I am a C expert, not a C master.

2 comments:

Anonymous said...

Grasshopper,
I see part of your problem.
You keep trying to spell foot like this foo_t. There is no underscore in this word.
The other part of your problemis there are not enough *. const is a diva try ****** and maybe there will be paparazzi as well as improved performance.
--See Master Extrodinare
ps Today my verification word is bingstai, what is its definintion?

Unknown said...

IMHO
C++ distinction of a const/non-const method/arguments/variables express disrespect to the concept of interfaces. C++ spec. does not mention it is 'programming by interface'[which I would call collaboration by interface]. Same is the case with Java Collection Interface API where they could have separated non & non-const interfaces and didn't.