5

In semantic versioning the general rule is to increase the minor number only when backwards compatible functionalities are introduced, otherwise the major number must be increased instead. The same approach, but with a different arithmetic, is used by libtool.

I have a question concerning what is considered a backwards compatible change and what not.

Imagine I have written a library, and the public header of this library contains a typedef of a data type named foo. In version 1.0.0 this typedef looks like this:

typedef struct foo_t {
    int x;
    int y;
} foo;

Then I decide to change the data type, and in the next version it will look like this:

typedef struct foo_t {
    int x;
    int y;
    int z;
} foo;

I have only added one field to the structure foo_t. It would seem to be a backward compatible change, however the structure above is de facto another structure now. What I have done was not introducing a new function and leave untouched all the rest, but instead I have changed something that was already there.

The data type above is normally used for exchanging data with the library's functions, however the user might have used it with other purposes. If the user had written a program using version 1.0.0 and the last change constitutes a backward compatible change, the user's program must compile also with this new version.

How will this new version be called, 1.1.0 or 2.0.0?

EDIT

You can read further developments of this discussion here.

madmurphy
  • 1,451
  • 11
  • 20

1 Answers1

5

It would be a major version change. Struct layouts are baked into end user programs. It is a breaking change to add or remove members; either way, the layout has changed.

Quoting from the Linux Program Library HOWTO โ€” ยง3.6. Incompatible Libraries:

When a new version of a library is binary-incompatible with the old one the soname needs to change. In C, there are four basic reasons that a library would cease to be binary compatible:

  1. The behavior of a function changes so that it no longer meets its original specification,

  2. Exported data items change (exception: adding optional items to the ends of structures is okay, as long as those structures are only allocated within the library).

  3. An exported function is removed.

  4. The interface of an exported function changes.

You say, "The data type above is normally used for exchanging data with the library's functions, however the user might have used it with other purposes." If the struct were used internally only and not exposed in the public API you'd be okay. But it sounds like the user could allocate a struct variable themselves, which means it's a breaking change.

You can protect your library from this type of churn by using opaque structs. Hide the contents of the structs and have the user just pass around pointers. This is exactly how FILE * works: the C standard doesn't define the layout of FILE and we as end users don't know what's inside of it.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • Thank you for the very good answer. I must not hide my `struct` to the user, so opaque structures are not an option. Major version change be it then! โ€“ madmurphy Mar 10 '19 at 13:57
  • Note that you may be able to avoid needing major version changes by making the size dynamic (so the user needs to query the library for the size to allocate), or by including a size or even a dynamic version id in the object itself. With the latter, the library will need to check that size/version to know what version of object it is dealing with (more work), but may be worth it if you anticipate wanting to change the struct in the future. โ€“ Chris Dodd Mar 10 '19 at 20:20