Migration to 8.4: CONSTification

As approved in TIP 27 [L1 ], several of the public C functions provided by the Tcl and Tk libraries have had their declarations augmented with the addition of CONST modifiers on pointers. If you have C code, either an application or a package, that makes calls into the Tcl or Tk libraries, you may find that this code that compiled against the public header files (tcl.h and tk.h) of releases 8.3.* of Tcl and Tk will no longer compile against the public header files of releases 8.4*. This page is meant to guide you through making the required changes to your code to restore the ability to compile with the new headers.

There are two classes of people who will confront this problem. First are those who have written their own programs or libraries that link the Tcl/Tk libraries. Second are those who have acquired such source code from somebody else, and just need to get it to compile. The latter group is not likely interested in the finer points of programming, or arguments for/against these source incompatibilties. They just want to get a working compile. So I address that community first:

Step by step: getting your code to compile against Tcl/Tk 8.4 ...while being as lazy as possible...

Step 1: Upgrade to Tcl/Tk 8.4.0, or better.

If you have alpha or beta releases of Tcl/Tk 8.4, now is the time to replace them with the true 8.4.0 releases.

Step 2: Get updated code.

OK, so you have code that doesn't compile with 8.4 headers. Check back where you got it and see if an updated release is available that will compile. Better to get the fixes from the author/vendor than have to improvise some yourself.

Step 3: Define USE_NON_CONST

OK, so there are no updates available. The incompatibilities between 8.3 and 8.4 headers are completely removed if the symbol USE_NON_CONST is defined when the header files are included. If you have code that compiled with 8.3 headers, defining that symbol will make the same code compile with the 8.4 headers.

The simplest way to do this is to add -DUSE_NON_CONST to the CFLAGS definition during the compile, but that assumes a conventional Makefile. The surest way to do this is to find all the instances of

   #include <tcl.h>

in the source code and insert

   #define USE_NON_CONST

before them.

Step 4: There is no Step 4

That's it! It should work. If not, turn to the provider of the broken package for more help.

CONST migration for package/application authors

For those of you who maintain code that uses the Tcl C API, there are a number of options for dealing with the source incompatibilities between the 8.3 and 8.4 headers. You can select the most appropriate solution based on the effort required, time available, and status of your code.

First, as background, let's review the 3 kinds of changes that have been made regarding CONST between releases 8.3 and 8.4 of Tcl/Tk.

Type I. Some routines that accepted only writable strings (char *) now are defined to accept read-only strings (const char *) as well, having added a promise that they will not write on the string passed in.

For example, what was:

   int Tcl_Eval(Tcl_Interp *interp, char *script);  /* 8.3 */

is now:

   int Tcl_Eval(Tcl_Interp *interp, const char *script); /* 8.4 */

If you had code that worked with the 8.3 headers, the script argument passed in must have been a writable string. The 8.4 interface is happy to accept a writable string as well, so there will be no compiler error due to a change of Type I. Your 8.3-compatible code will need not make any changes to be 8.4-compatible.

Type II. Some routines that returned a string were defined to return a writable string, even though the returned string was really owned by the Tcl library, and documentation instructed not to write on it. Now those routines have prototypes that explicitly label the returned string to be read-only.

For example, what was:

   char * Tcl_GetHostName();  /* 8.3 */

is now:

   const char * Tcl_GetHostName();  /* 8.4 */

If your code assigns the return value of Tcl_GetHostName to a variable of type char *, that code will cause a compiler error when compiling against the 8.4 headers. This is actually a helpful error. As noted, you were never supposed to write to the string returned by Tcl_GetHostName, so there is no reason to assign that string to a variable that would allow you to write to it. Adapting your code should be a simple matter of changing the type of the variable in which you store the return value from char * to const char *. Once your code is so adapted, it will compile against both 8.3 and 8.4 headers.

Type III. The third type is the most difficult because the changes of this type force you to make a choice between supporting the 8.3 prototype, or the 8.4 prototype. Compilers cannot reconcile the difference between them without help.

Type IIIa. Some routines that accepted an argv style array of strings now expect to receive an argv style array of read-only strings.

For example, what was:

   char * Tcl_Merge(int argc, char **argv); /* 8.3 */

is now:

   char * Tcl_Merge(int argc, const char * const *argv); /* 8.4 */

Type IIIb. Some of the typedefs that determine the prototypes of routines to be written by extensions to Tcl have been changed. Some compilers include the const-ness of arguments when checking the type of function pointers, so these changes can also be irreconcilable.

For example, what was:

   typedef char *(Tcl_VarTraceProc) (ClientData clientData,
        Tcl_Interp *interp, char *part1,
        char *part2, int flags);

is now:

   typedef char *(Tcl_VarTraceProc) (ClientData clientData,
        Tcl_Interp *interp, const char *part1,
        const char *part2, int flags);

For all Type III interfaces, you will have to make a choice whether your code will be written to the 8.3 interface or the 8.4 interface, and #define appropriate symbols to force the headers to declare compatible prototypes for your choice.

With that as background, here are your alternatives for migration.

Option 1. Make no changes to your code. #define USE_NON_CONST

When you #define USE_NON_CONST, all the consts of Type II and Type III are disabled. This leaves only the Type I changes active, and they can cause no compiler errors. This is the option recommended for users of code, who just want to get something working without becoming maintainers themselves. This might also be a suitable option for a developer with limited time who just needs to get an 8.4-compatible version out the door quickly, and can take the time later for a fuller migration. It might also be suitable for older projects that are no longer actively developed, but want to be made able to work with the 8.4 Tcl release.

Option 2. Update to deal with Type II changes. #define USE_COMPAT_CONST

When you #define USE_COMPAT_CONST, all the consts of Type III are disabled. The Type II changes are active, so you'll need to be sure that all read-only strings returned by Tcl get stored only in const char * variables, or passed on only to other routines that accept a const char * argument. Once you've made those updates, the resulting code will compile against both 8.3 and 8.4 headers. This option is a good idea for those developers who have any time at all, and who are continuing to update their code, because any changes made under this option are likely to be bug fixes.

Option 3. Move completely to 8.4 interfaces. Drop 8.3 support.

With no #defines, you get all the 8.4 prototypes, including those that cannot be reconciled with their 8.3 predecessors. This will involve make several changes to your code to satisy the requirements of the Type III changes. The compiler will guide you. This option is suitable for developers that will make use of new features provided in the 8.4 releases of Tcl/Tk. Since they will require the 8.4 headers anyway, they should write exclusively to the new interface.

NOTE: Each of these 3 options can be applied to your project as a whole (with a -DUSE_X_CONST added to CFLAGS), or can be applied on a file by file basis (with #define USE_X_CONST), so you have fine control over how to update your code bit by bit as suitable for you.

Finally, I should note another option that some folks have found to be useful.

Option 4. Use the internal symbol CONST84 to continue pre-8.4 support

Some developers want to adopt all the 8.4 interfaces, but are not prepared to drop support of pre-8.4 releases of Tcl/Tk. They have made use of the symbol CONST84 directly in their code. That symbol is the one used by the USE_NON_CONST and USE_COMPAT_CONST symbols to disable the Type III changes. It was meant to be an internal detail, but it can be effectively used if you wish.

In your own code, include:

    #ifndef CONST84
    #   define CONST84

Then, wherever there is a const of Type III, use the symbol CONST84 in your code instead of const. In this way, when compiling against the 8.4 headers, you will be using the const-ified interfaces, but when compiling against the 8.3 headers, you will get the original interfaces.

See also Changes in Tcl/Tk 8.4

See also [L2 ] and [L3 ] (and subsequent messages in the thread) for a fairly detailed report of the TIP 27 impact on two extensions.

Summary: In one case, adding CONST84 in a few places did 90% of the job, a few minor changes to the code fixed the rest. In another case, -DUSE_NON_CONST was necessary, since the extension receives data from parts of the Core that were not CONSTified and passes it on to other parts that were. Update: Post-8.4b1, more parts of the Core were CONSTified; after these changes, it was possible to fully CONSTify the second extension without needing -DUSE_NON_CONST (using Option 4, to retain source-level compatibility with 8.3 and 8.4 headers).