I’m proposing implementing strong typedefs in Clang, a compile-time type
safety feature that creates distinct types to prevent implicit conversions
between types with the same underlying representation, catching semantic errors
at compile time with zero runtime overhead.
My design goals are as follows:
- Prevents implicit conversions between different strong types
- Prevents implicit conversions from strong types to underlying types
- Allows implicit conversions from underlying types to strong types (initialization)
- Zero runtime cost - desugars to underlying type in LLVM IR
- Works with arithmetic, pointers, arrays, and all standard C operations
- C and C++ support
- This is a language extension, not trying to go directly to the standard
Motivations
Type confusion bugs are a common source of errors in C code. When different
semantic concepts share the same underlying type, the compiler cannot
distinguish between them.
Here’s some examples of type classes that could be better discriminated with strong typedefs:
- Unit confusion: meters vs yards, seconds vs milliseconds
- ID confusion: mixing user IDs, product IDs, session IDs
- Currency handling: USD vs EUR amounts
- Security boundaries: trusted vs untrusted data, sanitized vs raw strings
- Domain-specific types: file descriptors vs PIDs, different kinds of handles
Proposed Implementation
Syntax
Strong typedefs use the strong attribute on typedef declarations:
__attribute__((strong)) typedef double Meters;
__attribute__((strong)) typedef double Yards;
__attribute__((strong)) typedef int UserId;
The attribute can be placed before or after the typedef name:
__attribute__((strong)) typedef int T1;
typedef int T2 __attribute__((strong));
The [[strong]] attribute syntax is also supported:
[[strong]] typedef double Meters;
[[strong]] typedef double Yards;
[[strong]] typedef int UserId;
We may also (or exclusively) want a non-attribute spelling (I.e., keyword).
Type Compatibility Rules
The following rules define strong typedef compatibility:
-
Strong → Strong (same): Compatible
UserId uid1 = 42; UserId uid2 = uid1; // OK -
Strong → Strong (different): Incompatible
__attribute__((strong)) typedef int UserId; __attribute__((strong)) typedef int ProductId; UserId uid = 42; ProductId pid = uid; // ERROR -
Non-strong → Strong: Compatible (initialization)
UserId uid = 42; // OK: implicit conversion from int -
Strong → Non-strong: Incompatible
UserId uid = 42; int i = uid; // ERROR -
Explicit casts: Always allowed
UserId uid = 42; ProductId pid = (ProductId)uid; // OK int i = (int)uid; // OK
Questions
- How should we handle ABI differences? Should there be any differences between a strong typedef parameter and its underlying type for the purposes of mangling and other ABI considerations?
- How should strong typedefs interact with
_Generic? - Any C++ concerns? templates, overloads, SFINAE
Links
- WG14 proposal: strong-typedef
- Another WG14 proposal: new-qualifiers