# interface99 **Repository Path**: doveyxia/interface99 ## Basic Information - **Project Name**: interface99 - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-06-30 - **Last Updated**: 2025-06-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README

Interface99

Full-featured interfaces inspired by Rust and Golang. Multiple inheritance, superinterfaces, and default implementations supported. No external tools required, pure C99.
Shape
```c #include #include #define Shape_IFACE \ vfunc( int, perim, const VSelf) \ vfunc(void, scale, VSelf, int factor) interface(Shape); ```
Rectangle
Triangle
```c typedef struct { int a, b; } Rectangle; int Rectangle_perim(const VSelf) { VSELF(const Rectangle); return (self->a + self->b) * 2; } void Rectangle_scale(VSelf, int factor) { VSELF(Rectangle); self->a *= factor; self->b *= factor; } impl(Shape, Rectangle); ```
```c typedef struct { int a, b, c; } Triangle; int Triangle_perim(const VSelf) { VSELF(const Triangle); return self->a + self->b + self->c; } void Triangle_scale(VSelf, int factor) { VSELF(Triangle); self->a *= factor; self->b *= factor; self->c *= factor; } impl(Shape, Triangle); ```
Test
```c void test(Shape shape) { printf("perim = %d\n", VCALL(shape, perim)); VCALL(shape, scale, 5); printf("perim = %d\n", VCALL(shape, perim)); } int main(void) { Shape r = DYN_LIT(Rectangle, Shape, {5, 7}); Shape t = DYN_LIT(Triangle, Shape, {10, 20, 30}); test(r); test(t); } ```
(Based on [`examples/shape.c`](examples/shape.c).)
Output ``` perim = 24 perim = 120 perim = 60 perim = 300 ```
## Highlights - **Minimum boilerplate.** Forget about maintaining virtual tables -- just write `impl(Shape, Rectangle)` and Interface99 will do it for you! - **Portable.** Everything you need is a standard-conforming C99 compiler; neither the standard library, nor compiler/platform-specific functionality or VLA are required. - **Predictable.** Interface99 comes with formal [code generation semantics], meaning that the generated data layout is guaranteed to always be the same. - **Comprehensible errors.** Interface99 is [resilient to bad code]. - **Battle-tested.** Interface99 is used at [OpenIPC] to develop real-time streaming software for IP cameras; this includes an [RTSP 1.0 implementation] along with ~50k lines of private code. [code generation semantics]: #semantics [resilient to bad code]: #q-what-about-compile-time-errors [OpenIPC]: https://openipc.org/ [RTSP 1.0 implementation]: https://github.com/OpenIPC/smolrtsp/ ## Features | Feature | Status | Description | |---------|--------|-------------| | [Multiple interface inheritance](examples/read_write.c) | ✅ | A type can inherit multiple interfaces at the same time. | | [Superinterfaces](examples/airplane.c) | ✅ | One interface can require a set of other interfaces to be implemented as well. | | [Marker interfaces](examples/marker.c) | ✅ | An interface with no functions. | | [Single/Dynamic dispatch](examples/shape.c) | ✅ | Determine a function to be called at runtime based on `self`. | | Multiple dispatch | ❌ | Determine a function to be called at runtime based on multiple arguments. Likely to never going to be implemented. | | [Dynamic objects of multiple interfaces](examples/read_write_both.c) | ✅ | Given interfaces `Foo` and `Bar`, you can construct an object of both interfaces, `FooBar obj`. | | [Default implementations](examples/default_impl.c) | ✅ | Some interface functions may be given default implementations. A default function can call other functions and vice versa. | | Data and implementation separation | ✅ | New interfaces can be implemented for existing types. | ## Installation Interface99 consists of one header file `interface99.h` and one dependency [Metalang99]. To use it in your project, you need to: [Metalang99]: https://github.com/hirrolot/metalang99 1. Add `interface99` and `metalang99/include` to your include directories. 2. Specify [`-ftrack-macro-expansion=0`] (GCC) or [`-fmacro-backtrace-limit=1`] (Clang) to avoid useless macro expansion errors. [`-ftrack-macro-expansion=0`]: https://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html [`-fmacro-backtrace-limit=1`]: https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-fmacro-backtrace-limit If you use CMake, the recommended way is [`FetchContent`]: [`FetchContent`]: https://cmake.org/cmake/help/latest/module/FetchContent.html ```cmake include(FetchContent) FetchContent_Declare( interface99 URL https://github.com/hirrolot/interface99/archive/refs/tags/vx.y.z.tar.gz # vx.y.z ) FetchContent_MakeAvailable(interface99) target_link_libraries(MyProject interface99) # Disable full macro expansion backtraces for Metalang99. if(CMAKE_C_COMPILER_ID STREQUAL "Clang") target_compile_options(MyProject PRIVATE -fmacro-backtrace-limit=1) elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU") target_compile_options(MyProject PRIVATE -ftrack-macro-expansion=0) endif() ``` (By default, `interface99/CMakeLists.txt` downloads Metalang99 [v1.13.5](https://github.com/hirrolot/metalang99/releases/tag/v1.13.5) from the GitHub releases; if you want to override this behaviour, you can do so by invoking [`FetchContent_Declare`] earlier.) [`FetchContent_Declare`]: https://cmake.org/cmake/help/latest/module/FetchContent.html#command:fetchcontent_declare Optionally, you can [precompile headers] in your project that rely on Interface99. This will decrease compilation time, because the headers will not be compiled each time they are included. [precompile headers]: https://en.wikipedia.org/wiki/Precompiled_header Happy hacking! ## Tutorial This section is based on a collection of well-documented [examples](examples/), each of which demonstrates one specific aspect of Interface99. ### Basic usage 1. **Interface definition.** Syntax: [`interface(Shape);`](#interface) An interface definition expands to a virtual table structure and a so-called _interface object type_. In the case of [`examples/shape.c`](examples/shape.c): ```c // interface(Shape); typedef struct ShapeVTable ShapeVTable; typedef struct Shape Shape; struct ShapeVTable { int (*perim)(const VSelf); void (*scale)(VSelf, int factor); }; struct Shape { void *self; const ShapeVTable *vptr; }; ``` Here, `Shape.self` is the pointer to an object whose type implements `Shape`, and `Shape.vptr` points to a corresponding virtual table instance. Inside `ShapeVTable`, you can observe the mysterious [`VSelf`](#vselfvself) bits -- they expand to parameters of type `void * restrict` (with extra `const` for `perim`); when calling these methods, Interface99 will substitute `Shape.self` for these parameters. Usually, interface definitions go in `*.h` files. 2. **Implementation definition.** | Linkage | Syntax | |---------|--------| | Internal | [`impl(Shape, Rectangle);`](#impl) | | External | [`implExtern(Shape, Rectangle);`](#implExtern) | An implementation definition expands to nothing but a virtual table instance of a particular implementer. In the case of `examples/shape.c`: ```c // impl(Shape, Rectangle); static const ShapeVTable Rectangle_Shape_impl = { .perim = Rectangle_perim, .scale = Rectangle_scale, }; ``` (If you were using [`implExtern`](#implExtern), this definition would be `extern` likewise.) Note that inside function implementations, we use [`VSELF`](#vselfvself), which simply casts the parameter introduced by `VSelf` to a user-defined type (`const Rectangle` or `Rectangle` in our case): ```c int Rectangle_perim(const VSelf) { VSELF(const Rectangle); return (self->a + self->b) * 2; } void Rectangle_scale(VSelf, int factor) { VSELF(Rectangle); self->a *= factor; self->b *= factor; } ``` 3. **Dynamic dispatch.** Once an interface and its implementations are both generated, it is time to instantiate an interface object and invoke some functions upon it. First of all, to instantiate `Shape`, use the [`DYN_LIT`](#DYN_LIT) macro: ```с Shape r = DYN_LIT(Rectangle, Shape, {5, 7}); test(r); ``` Here, `DYN_LIT(Rectangle, Shape, {5, 7})` creates `Shape` by assigning `Shape.self` to `&(Rectangle){5, 7}` and `Shape.vptr` to the aforementioned `&Rectangle_Shape_impl`. Eventually, you can accept `Shape` as a function parameter and perform dynamic dispatch through the [`VCALL`](#vcall_) macro: ```c void test(Shape shape) { printf("perim = %d\n", VCALL(shape, perim)); VCALL(shape, scale, 5); printf("perim = %d\n", VCALL(shape, perim)); } ``` Finally, just a few brief notes: - Besides `VCALL`, you also have `VCALL_OBJ`, `VCALL_SUPER`, and `VCALL_SUPER_OBJ`. They all serve a different purpose; for more information, please refer to [their documentation](#vcall_). - In practice, [`DYN`](#DYN) is used more often than [`DYN_LIT`](#DYN_LIT); it just accepts an ordinary pointer instead of an initialiser list, which means that you can `malloc` it beforehand. - If your virtual function does not accept `self`, you can invoke it as `obj.vptr->foo(...)`. - If you want to call an interface function on some concrete type, just write `VTABLE(T, Iface).foo(...)`. Congratulations, this is all you need to know to write most of the stuff! ### Superinterfaces Interface99 has the feature called superinterfaces, or interface requirements. [`examples/airplane.c`](examples/airplane.c) demonstrates how to extend interfaces with new functionality: ```c #define Vehicle_IFACE \ vfunc(void, move_forward, VSelf, int distance) \ vfunc(void, move_back, VSelf, int distance) interface(Vehicle); #define Airplane_IFACE \ vfunc(void, move_up, VSelf, int distance) \ vfunc(void, move_down, VSelf, int distance) #define Airplane_EXTENDS (Vehicle) interface(Airplane); ``` (Note that `#define Airplane_EXTENDS` must appear prior to `interface(Airplane);`.) Here, `Airplane` extends `Vehicle` with the new functions `move_up` and `move_down`. Everywhere you have `Airplane`, you can also operate `Vehicle`: ```c Airplane my_airplane = DYN_LIT(MyAirplane, Airplane, {.x = 0, .y = 0}); VCALL_SUPER(my_airplane, Vehicle, move_forward, 10); VCALL_SUPER(my_airplane, Vehicle, move_back, 3); ``` Internally, Interface99 embeds superinterfaces' virtual tables into those of subinterfaces, thereby forming a _virtual table hierarchy_. For example, you can specify `Repairable` and `Armoured` along with `Vehicle`, and they all will be included into `AirplaneVTable` like so: ```c // #define Airplane_EXTENDS (Vehicle, Repairable, Armoured) typedef struct AirplaneVTable { void (*move_up)(VSelf, int distance); void (*move_down)(VSelf, int distance); const VehicleVTable *Vehicle; const RepairableVTable *Repairable; const ArmouredVTable *Armoured; } AirplaneVTable; ``` ### Default implementations Sometimes we wish to define default behaviour for several implementers; this is supported by _default implementations_. Take a look at [`examples/default_impl.c`](examples/default_impl.c). In this example, we define the interface `Droid`: ```c #define Droid_IFACE \ vfunc(const char *, name, void) \ vfuncDefault(void, turn_on, Droid droid) interface(Droid); ``` The macro `vfuncDefault` tells Interface99 to use the default implementation for `turn_on` automatically. But where is it located? Here: ```c void Droid_turn_on(Droid droid) { printf("Turning on %s...\n", droid.vptr->name()); } ``` As you can see, default implementations follow a strict naming convention, `_` , which provides Interface99 with sufficient information to generate a virtual table. Additionally, as a developer, you can also rely on this convention and call a default function of a third-party interface. For `C_3PO`, we use the default implementation of `turn_on`, and the resulting virtual table would look like this: ```c static const DroidVTable C_3PO_Droid_impl = { .name = C_3PO_name, .turn_on = Droid_turn_on, }; ``` But for `R2_D2`, we use a custom implementation `R2_D2_turn_on`: ```c void R2_D2_turn_on(Droid droid) { Droid_turn_on(droid); puts("Waaaaoow!"); } #define R2_D2_turn_on_CUSTOM () impl(Droid, R2_D2); ``` (`R2_D2_turn_on_CUSTOM` tells Interface99 to use the custom implementation instead of the default one; this is because it is impossible to detect at compile-time whether a specific function is defined or not.) And the virtual table would be: ```c static const DroidVTable R2_D2_Droid_impl = { .name = R2_D2_name, .turn_on = R2_D2_turn_on, }; ``` Please, note that you have to specify `()` for the `*_CUSTOM` attribute; do not leave it empty. ## Syntax and semantics Having a well-defined semantics of the macros, you can write an FFI which is quite common in C. ### EBNF syntax ```ebnf ::= "interface(" ")" ; ::= ; ::= | ; ::= "vfunc(" "," "," ")" ; ::= "vfuncDefault(" "," "," ")" ; ::= ; ::= ; ::= ; ::= "impl(" "," ")" ; ::= "implExtern(" "," ")" ; ::= "declImpl(" "," ")" ; ::= "declImplExtern(" "," ")" ; ::= ; ::= "DYN(" "," "," ")" ; ::= "DYN_LIT(" "," "," "{" "}" ")" ; ::= "VTABLE(" "," ")" ; ::= "VSelf" ; ::= "VSELF(" ")" ; (* must be an expression of an interface object type. *) ::= "VCALL(" "," ")" ; ::= "VCALL_OBJ(" "," ")" ; ::= "VCALL_SUPER(" "," "," ")" ; ::= "VCALL_SUPER_OBJ(" "," "," ")" ; ::= [ "," ] ; ::= ; ```
Note: shortened vs. postfixed versions Each listed identifier in the above grammar corresponds to a macro name defined by default -- these are called _shortened versions_. On the other hand, there are also _postfixed versions_ (`interface99`, `impl99`, `vfunc99`, etc.), which are defined unconditionally. If you want to avoid name clashes caused by shortened versions, define `IFACE99_NO_ALIASES` before including `interface99.h`. Library headers are strongly advised to use the postfixed macros, but without resorting to `IFACE99_NO_ALIASES`.
Notes: - For every interface ``, the macro `_IFACE` must expand to `{ }*`. - For any interface, a macro `_EXTENDS` can be defined, which must expand to `"(" { "," }* ")"`. - For any interface function implementation, a macro `__CUSTOM` can be defined, which must expand to `"()"`. [Clang-Format]: https://clang.llvm.org/docs/ClangFormatStyleOptions.html ### Semantics (It might be helpful to look at the [generated output](https://godbolt.org/z/Gr6f7TM83) of [`examples/shape.c`](examples/shape.c).) #### `interface` Expands to ``` typedef struct VTable VTable; typedef struct ; struct VTable { // Only if is a marker interface without superinterfaces: char dummy; 0 (*0)(0); ... N (*N)(N); const 0VTable *; ... const NVTable *; }; struct { void *self; const VTable *vptr; } ``` (`char dummy;` is needed for an empty `VTable` because a structure must have at least one member, according to C99.) I.e., this macro defines a virtual table structure for ``, as well as the structure `` that is polymorphic over `` implementers. This is generated in two steps: - **Function pointers**. For each `I` specified in the macro `_IFACE`, the corresponding function pointer is generated. - **Requirements obligation.** If the macro `_EXTENDS` is defined, then the listed requirements are generated to obligate `` implementers to satisfy them. #### `impl` Expands to ``` static const VTable VTABLE(, ) = { // Only if is a marker interface without superinterfaces: .dummy = '\0', 0 = either _0 or _0, ... N = either _N or _N, 0 = &VTABLE(, 0), ... N = &VTABLE(, N), } ``` I.e., this macro defines a virtual table instance of type `VTable` for ``. It is generated in two steps: - **Function implementations.** If `I` is defined via `vfuncDefault` and `_I_CUSTOM` is **not** defined, `_I` is generated (default implementation). Otherwise, `_I` is generated (custom implementation). - **Requirements satisfaction.** If the macro `_EXTENDS` is defined, then the listed requirements are generated to satisfy ``. #### `implExtern` The same as [`impl`](#impl) but generates an `extern` definition instead of `static`. #### `declImpl` Expands to `static const VTable VTABLE(, )`, i.e., it declares a virtual table instance of `` of type `VTable`. #### `declImplExtern` The same as [`declImpl`](#declImpl) but generates an `extern` declaration instead of `static`. #### `DYN` Expands to an expression of type ``, with `.self` initialised to `` and `.vptr` initialised to `&VTABLE(, )`. `` is guaranteed to be evaluated only once. #### `DYN_LIT` `DYN_LIT(, , ...)` expands to `DYN(, , &()...)`. The `...` must take the form of an initialiser list in [compound literals]. [compound literals]: https://en.cppreference.com/w/c/language/compound_literal #### `VTABLE` Expands to `__impl`, i.e., a virtual table instance of `` of type `VTable`. #### `VSelf`/`VSELF` `VSelf` is an object-like macro that expands to a function parameter of type `void * restrict`, with an implementation-defined name. In order to downcast this parameter to an implementer type, there exists a function-like macro `VSELF`. `VSELF(T)` which brings a variable `self` of type `T * restrict` into the scope, and initialises it to the `VSelf`-produced parameter name casted to `T * restrict`. `VSelf` can be used on any position for any virtual function, however, it only makes sense to use it as a first parameter. `VSELF(T)` can be used everywhere inside a function with the `VSelf` parameter. #### `VCALL_*` The `VCALL_*` macros are meant to **call** a **v**irtual method, which is a `vfunc`/`vfuncDefault` that accepts either `VSelf` or an interface object (of a containing interface type) as a first parameter. For methods accepting `VSelf`, there exist `VCALL` and `VCALL_SUPER`: - `VCALL(obj, func)` => `obj.vptr->func(obj.self)`. - `VCALL(obj, func, args...)` => `obj.vptr->func(obj.self, args...)`. - `VCALL_SUPER(obj, superiface, func)` => `obj.vptr->superiface->func(obj.self)`. - `VCALL_SUPER(obj, superiface, func, args...)` => `obj.vptr->superiface->func(obj.self, args...)`. For methods accepting an interface object, there are `VCALL_OBJ` and `VCALL_SUPER_OBJ`: - `VCALL_OBJ` is the same as `VCALL` except that it passes `obj` to `func` instead of `obj.self`. - `VCALL_SUPER_OBJ` is the same as `VCALL_SUPER` except that it passes `(superiface){obj.self, obj.vptr->superiface}` to `func` instead of `obj.self`. ## Miscellaneous - The macros `IFACE99_MAJOR`, `IFACE99_MINOR`, `IFACE99_PATCH`, `IFACE99_VERSION_COMPATIBLE(x, y, z)`, and `IFACE99_VERSION_EQ(x, y, z)` have the [same semantics as of Metalang99](https://metalang99.readthedocs.io/en/latest/#version-manipulation-macros). - For each macro using `ML99_EVAL`, Interface99 provides its [Metalang99-compliant](https://metalang99.readthedocs.io/en/latest/#definitions) counterpart which can be used inside derivers and other Metalang99-compliant macros: | Macro | Metalang99-compliant counterpart | |----------|----------| | `interface` | `IFACE99_interface` | | `impl` | `IFACE99_impl` | | `implExtern` | `IFACE99_implExtern` | (An [arity specifier] and [desugaring macro] are provided for each of the above macros.) [arity specifier]: https://hirrolot.gitbook.io/metalang99/partial-application [desugaring macro]: https://metalang99.readthedocs.io/en/latest/#definitions ## Guidelines - Write `impl(...)`/`implExtern(...)` right after all functions are implemented; do not gather all implementation definitions in a single place. - If you use [Clang-Format], it can be helpful to add `vfunc` and `vfuncDefault` to the `StatementMacros` vector (see [our `.clang-format`](.clang-format)). It will instruct the formatter to place them onto different lines. ## Pitfalls - Both interfaces that you implement for a single type can have a function with the same name, thus resulting in a name collision. However, you can elegantly workaround like this: ```c // `MyType_Iface1_foo` function definition here... #define Iface1_foo MyType_Iface1_foo impl(Iface1, MyType); #undef Iface1_foo // `MyType_Iface2_foo` function definition here... #define Iface2_foo MyType_Iface2_foo impl(Iface2, MyType); #undef Iface2_foo ``` The same holds for custom implementations: ```c // Use a custom implementation for `Iface1::bar`. #define MyType_bar_CUSTOM () impl(Iface1, MyType); #undef MyType_bar_CUSTOM // Use the default `Iface2::bar`. impl(Iface2, MyType); ``` ## Design choices The design of Interface99 may raise some questions. In this section, you may find answers why it was designed in this way. ### `VCALL_*` Instead of using the `VCALL_*` macros, we could instead generate functions that accept an interface object as a first parameter, with the rest of parameters being arguments to a particular method: ```c void Shape_scale(Shape shape, int factor) { shape.vptr->scale(shape.self, factor); } ``` But this approach does not work for superinterfaces' methods, as well as for methods accepting an interface object instead of `VSelf` or a combination thereof. For this reason, I decided to stick to more expressive `VCALL_*` macros, although at the cost of some IDE support. ### `self` type safety Since there can be many specific implementations of a virtual method (like `Rectangle_scale` or `Triangle_scale`), `self` **must** be of type `void *`. But the problem is that in concrete implementations, we still want `self` to be of some concrete type; and since `void *` and `T *` may be incompatible types, assigning a concrete method accepting `T *` to a virtual method field [results in UB](https://stackoverflow.com/questions/559581/casting-a-function-pointer-to-another-type). To solve the problem, we may want to generate untyped wrapper functions that accept `void *restrict self` and pass the downcasted version to the underlying method: ```c void Rectangle_scale_wrapper(void *restrict self, int factor) { Rectangle_scale((Rectangle * restrict)self, factor); } ``` But the reason we do **not** do this is that in C99, it is impossible to differentiate `void` from other types; if the return type is `void`, we must not emit `return` with an expression, otherwise, we **must**. We could come up with something like `vfuncVoid` and `vfuncDefaultVoid` but this would increase the learning curve and complicate the design and implementation of Interface99. However, casting untyped `self` to a particular type is still quite unpleasant. The best thing I came up with is the `VSelf` and `VSELF(T)` mechanism, which nonetheless works quite well. ## Credits Thanks to Rust and Golang for their implementations of traits/interfaces. ## Blog posts - [_Comparing Golang and Interface99_](https://www.reddit.com/r/C_Programming/comments/tgm5ft/comparing_golang_and_interface99/) - [_What’s the Point of the C Preprocessor, Actually?_](https://hirrolot.github.io/posts/whats-the-point-of-the-c-preprocessor-actually.html) - [_Macros on Steroids, Or: How Can Pure C Benefit From Metaprogramming_](https://hirrolot.github.io/posts/macros-on-steroids-or-how-can-pure-c-benefit-from-metaprogramming.html) - [_Extend Your Language, Don’t Alter It_](https://hirrolot.github.io/posts/extend-your-language-dont-alter-it.html) ## Release procedure 1. Update `IFACE99_MAJOR`, `IFACE99_MINOR`, and `IFACE99_PATCH` in `interface99.h`. 2. Update `CHANGELOG.md`. 3. Release the project in [GitHub Releases]. [GitHub Releases]: https://github.com/hirrolot/interface99/releases ## FAQ ### Q: Why use C instead of Rust/Zig/whatever else? A: See [Datatype99's README >>](https://github.com/hirrolot/datatype99#q-why-use-c-instead-of-rustzigwhatever-else). ### Q: Why not third-party code generators? A: See [Metalang99's README >>](https://github.com/hirrolot/metalang99#q-why-not-third-party-code-generators). ### Q: How does it work? A: Interface99 is implemented upon [Metalang99], a preprocessor metaprogramming library that allows enriching pure C with some custom syntax sugar. ### Q: Does it work on C++? A: Yes, C++11 and onwards is supported. ### Q: How Interface99 differs from similar projects? A: - **Less boilerplate.** In particular, Interface99 deduces function implementations from the context, thus improving code maintenance. To my knowledge, no other alternative can do this. - **Small.** Interface99 only features the software interface concept, no less and no more -- it does not bring all the other fancy OOP stuff, unlike [GObject] or [COS]. - **Depends on Metalang99.** Interface99 is built upon [Metalang99], the underlying metaprogramming framework. With Metalang99, you can also use [Datatype99]. Other worth-mentioning projects: - [typeclass-interface-pattern], though it is rather a general idea than a ready-to-use implementation. - [OOC] -- a book about OO programming in ANSI C. [`obj.h`]: https://github.com/small-c/obj.h [GObject]: https://developer.gnome.org/gobject/stable/ [COS]: http://ldeniau.web.cern.ch/ldeniau/cos.html [Datatype99]: https://github.com/hirrolot/datatype99 [typeclass-interface-pattern]: https://github.com/TotallyNotChase/typeclass-interface-pattern [OOC]: https://www.cs.rit.edu/~ats/books/ooc.pdf ### Q: What about compile-time errors? #### Error: missing interface implementation [`playground.c`] ```c #define Foo_IFACE vfunc(void, foo, int x, int y) interface(Foo); typedef struct { char dummy; } MyFoo; // Missing `void MyFoo_foo(int x, int y)`. impl(Foo, MyFoo); ``` [`/bin/sh`] ``` playground.c:12:1: error: ‘MyFoo_foo’ undeclared here (not in a function) 12 | impl(Foo, MyFoo); | ^~~~ ``` ---------- #### Error: improperly typed interface implementation [`playground.c`] ```c #define Foo_IFACE vfunc(void, foo, int x, int y) interface(Foo); typedef struct { char dummy; } MyFoo; void MyFoo_foo(const char *str) {} impl(Foo, MyFoo); ``` [`/bin/sh`] ``` playground.c:12:1: warning: initialization of ‘void (*)(int, int)’ from incompatible pointer type ‘void (*)(const char *)’ [-Wincompatible-pointer-types] 12 | impl(Foo, MyFoo); | ^~~~ playground.c:12:1: note: (near initialization for ‘MyFoo_Foo_impl.foo’) ``` ---------- #### Error: unsatisfied interface requirement [`playground.c`] ```c #define Foo_IFACE vfunc(void, foo, int x, int y) interface(Foo); #define Bar_IFACE vfunc(void, bar, void) #define Bar_EXTENDS (Foo) interface(Bar); typedef struct { char dummy; } MyBar; void MyBar_bar(void) {} // Missing `impl(Foo, MyBar)`. impl(Bar, MyBar); ``` [`/bin/sh`] ``` playground.c:19:1: error: ‘MyBar_Foo_impl’ undeclared here (not in a function); did you mean ‘MyBar_Bar_impl’? 19 | impl(Bar, MyBar); | ^~~~ | MyBar_Bar_impl ``` ---------- #### Error: typo in `DYN` [`playground.c`] ```c #define Foo_IFACE vfunc(void, foo, void) interface(Foo); typedef struct { char dummy; } MyFoo; void MyFoo_foo(void) {} impl(Foo, MyFoo); int main(void) { Foo foo = DYN(MyFoo, /* Foo */ Bar, &(MyFoo){0}); } ``` [`/bin/sh`] ``` playground.c: In function ‘main’: playground.c:14:28: error: ‘Bar’ undeclared (first use in this function) 14 | int main(void) { Foo foo = DYN(MyFoo, /* Foo */ Bar, &(MyFoo){0}); } | ^~~ playground.c:14:28: note: each undeclared identifier is reported only once for each function it appears in playground.c:14:31: error: expected ‘)’ before ‘{’ token 14 | int main(void) { Foo foo = DYN(MyFoo, /* Foo */ Bar, &(MyFoo){0}); } | ~~~^ | ) ``` ---------- #### Error: typo in `VTABLE` [`playground.c`] ```c #define Foo_IFACE vfunc(void, foo, void) interface(Foo); typedef struct { char dummy; } MyFoo; void MyFoo_foo(void) {} impl(Foo, MyFoo); int main(void) { FooVTable foo = VTABLE(/* MyFoo */ MyBar, Foo); } ``` [`/bin/sh`] ``` playground.c: In function ‘main’: playground.c:14:34: error: ‘MyBar_Foo_impl’ undeclared (first use in this function); did you mean ‘MyFoo_Foo_impl’? 14 | int main(void) { FooVTable foo = VTABLE(/* MyFoo */ MyBar, Foo); } | ^~~~~~ | MyFoo_Foo_impl ``` ---------- From my experience, nearly 95% of errors make sense. If an error is not comprehensible at all, try to look at generated code (`-E`). Hopefully, the [code generation semantics] is formally defined so normally you will not see something unexpected. ### Q: What about IDE support? ![Suggestion](images/suggestion.png) A: VS Code automatically enables suggestions of generated types but, of course, it does not support macro syntax highlighting. The sad part is that `VCALL` and its friends break go-to definitions and do not highlight function signatures, so we do intentionally [trade some IDE support for syntax conciseness](#vcall_-1). ### Q: Which compilers are tested? A: Interface99 is known to work on these compilers: - GCC - Clang - MSVC - TCC