Full-featured interfaces inspired by Rust and Golang. Multiple inheritance, superinterfaces, and default implementations supported. No external tools required, pure C99.
Shape |
#include <interface99.h>
#include <stdio.h>
#define Shape_IFACE \
vfunc( int, perim, const VSelf) \
vfunc(void, scale, VSelf, int factor)
interface(Shape); |
Rectangle |
Triangle |
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); |
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 |
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
.)
Output
perim = 24
perim = 120
perim = 60
perim = 300
-
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.
Feature | Status | Description |
---|---|---|
Multiple interface inheritance | ✅ | A type can inherit multiple interfaces at the same time. |
Superinterfaces | ✅ | One interface can require a set of other interfaces to be implemented as well. |
Marker interfaces | ✅ | An interface with no functions. |
Single/Dynamic dispatch | ✅ | 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 | ✅ | Given interfaces Foo and Bar , you can construct an object of both interfaces, FooBar obj . |
Default implementations | ✅ | 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. |
Interface99 consists of one header file interface99.h
and one dependency Metalang99. To use it in your project, you need to:
- Add
interface99
andmetalang99/include
to your include directories. - Specify
-ftrack-macro-expansion=0
(GCC) or-fmacro-backtrace-limit=1
(Clang) to avoid useless macro expansion errors.
If you use CMake, the recommended way is FetchContent
:
include(FetchContent)
FetchContent_Declare(
interface99
URL https://github.com/hirrolot/interface99/archive/refs/tags/v1.2.3.tar.gz # v1.2.3
)
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.2 from the GitHub releases; if you want to override this behaviour, you can do so by invoking FetchContent_Declare
earlier.)
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.
Happy hacking!
This section is based on a collection of well-documented examples, each of which demonstrates one specific aspect of Interface99.
- Interface definition.
Syntax: interface(Shape);
An interface definition expands to a virtual table structure and a so-called interface object type. In the case of examples/shape.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
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.
- Implementation definition.
Linkage | Syntax |
---|---|
Internal | impl(Shape, Rectangle); |
External | implExtern(Shape, Rectangle); |
An implementation definition expands to nothing but a virtual table instance of a particular implementer. In the case of examples/shape.c
:
// impl(Shape, Rectangle);
static const ShapeVTable Rectangle_Shape_impl = {
.perim = Rectangle_perim,
.scale = Rectangle_scale,
};
(If you were using implExtern
, this definition would be extern
likewise.)
Note that inside function implementations, we use VSELF
, which simply casts the parameter introduced by VSelf
to a user-defined type (const Rectangle
or Rectangle
in our case):
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;
}
- 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
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
macro:
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 haveVCALL_OBJ
,VCALL_SUPER
, andVCALL_SUPER_OBJ
. They all serve a different purpose; for more information, please refer to their documentation. - In practice,
DYN
is used more often thanDYN_LIT
; it just accepts an ordinary pointer instead of an initialiser list, which means that you canmalloc
it beforehand. - If your virtual function does not accept
self
, you can invoke it asobj.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!
Interface99 has the feature called superinterfaces, or interface requirements. examples/airplane.c
demonstrates how to extend interfaces with new functionality:
#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
:
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:
// #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;
Sometimes we wish to define default behaviour for several implementers; this is supported by default implementations.
Take a look at examples/default_impl.c
. In this example, we define the interface Droid
:
#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:
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, <iface>_<default-func-name>
, 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:
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
:
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:
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.
Having a well-defined semantics of the macros, you can write an FFI which is quite common in C.
<iface-def> ::= "interface(" <iface> ")" ;
<iface> ::= <ident> ;
<func> ::= <regular-func> | <default-func> ;
<regular-func> ::= "vfunc(" <func-ret-ty> "," <func-name> "," <func-params> ")" ;
<default-func> ::= "vfuncDefault(" <func-ret-ty> "," <func-name> "," <func-params> ")" ;
<func-ret-ty> ::= <type> ;
<func-name> ::= <ident> ;
<func-params> ::= <parameter-type-list> ;
<impl> ::= "impl(" <iface> "," <implementer> ")" ;
<implExtern> ::= "implExtern(" <iface> "," <implementer> ")" ;
<declImpl> ::= "declImpl(" <iface> "," <implementer> ")" ;
<declImplExtern> ::= "declImplExtern(" <iface> "," <implementer> ")" ;
<implementer> ::= <ident> ;
<dyn> ::= "DYN(" <implementer> "," <iface> "," <ptr> ")" ;
<dyn-lit> ::= "DYN_LIT(" <implementer> "," <iface> "," "{" <initializer-list> "}" ")" ;
<vtable> ::= "VTABLE(" <implementer> "," <iface> ")" ;
<vself-params> ::= "VSelf" ;
<vself-cast> ::= "VSELF(" <type> ")" ;
(* <expr> must be an expression of an interface object type. *)
<vcall> ::= "VCALL(" <expr> "," <func-name> <vcall-args> ")" ;
<vcall-obj> ::= "VCALL_OBJ(" <expr> "," <func-name> <vcall-args> ")" ;
<vcall-super> ::= "VCALL_SUPER(" <expr> "," <iface> "," <func-name> <vcall-args> ")" ;
<vcall-super-obj> ::= "VCALL_SUPER_OBJ(" <expr> "," <iface> "," <func-name> <vcall-args> ")" ;
<vcall-args> ::= [ "," <argument-expression-list> ] ;
<requirement> ::= <iface> ;
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
<iface>
, the macro<iface>_IFACE
must expand to{ <func> }*
. - For any interface, a macro
<iface>_EXTENDS
can be defined, which must expand to"(" <requirement> { "," <requirement> }* ")"
. - For any interface function implementation, a macro
<implementer>_<func-name>_CUSTOM
can be defined, which must expand to"()"
.
(It might be helpful to look at the generated output of examples/shape.c
.)
Expands to
typedef struct <iface>VTable <iface>VTable;
typedef struct <iface> <iface>;
struct <iface>VTable {
// Only if <iface> is a marker interface without superinterfaces:
char dummy;
<func-ret-ty>0 (*<func-name>0)(<func-params>0);
...
<func-ret-ty>N (*<func-name>N)(<func-params>N);
const <requirement>0VTable *<requirement>;
...
const <requirement>NVTable *<requirement>;
};
struct <iface> {
void *self;
const <iface>VTable *vptr;
}
(char dummy;
is needed for an empty <iface>VTable
because a structure must have at least one member, according to C99.)
I.e., this macro defines a virtual table structure for <iface>
, as well as the structure <iface>
that is polymorphic over <iface>
implementers. This is generated in two steps:
- Function pointers. For each
<func-name>I
specified in the macro<iface>_IFACE
, the corresponding function pointer is generated. - Requirements obligation. If the macro
<iface>_EXTENDS
is defined, then the listed requirements are generated to obligate<iface>
implementers to satisfy them.
Expands to
static const <iface>VTable VTABLE(<implementer>, <iface>) = {
// Only if <iface> is a marker interface without superinterfaces:
.dummy = '\0',
<func-name>0 = either <implementer>_<func-name>0 or <iface>_<func-name>0,
...
<func-name>N = either <implementer>_<func-name>N or <iface>_<func-name>N,
<requirement>0 = &VTABLE(<implementer>, <requirement>0),
...
<requirement>N = &VTABLE(<implementer>, <requirement>N),
}
I.e., this macro defines a virtual table instance of type <iface>VTable
for <implementer>
. It is generated in two steps:
- Function implementations. If
<func-name>I
is defined viavfuncDefault
and<implementer>_<func-name>I_CUSTOM
is not defined,<iface>_<func-name>I
is generated (default implementation). Otherwise,<implementer>_<func-name>I
is generated (custom implementation). - Requirements satisfaction. If the macro
<iface>_EXTENDS
is defined, then the listed requirements are generated to satisfy<iface>
.
The same as impl
but generates an extern
definition instead of static
.
Expands to static const <iface>VTable VTABLE(<implementer>, <iface>)
, i.e., it declares a virtual table instance of <implementer>
of type <iface>VTable
.
The same as declImpl
but generates an extern
declaration instead of static
.
Expands to an expression of type <iface>
, with .self
initialised to <ptr>
and .vptr
initialised to &VTABLE(<implementer>, <iface>)
.
<ptr>
is guaranteed to be evaluated only once.
DYN_LIT(<implementer>, <iface>, ...)
expands to DYN(<implementer>, <iface>, &(<implementer>)...)
. The ...
must take the form of an initialiser list in compound literals.
Expands to <implementer>_<iface>_impl
, i.e., a virtual table instance of <implementer>
of type <iface>VTable
.
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.
The VCALL_*
macros are meant to call a virtual 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 asVCALL
except that it passesobj
tofunc
instead ofobj.self
.VCALL_SUPER_OBJ
is the same asVCALL_SUPER
except that it passes(superiface){obj.self, obj.vptr->superiface}
tofunc
instead ofobj.self
.
-
The macros
IFACE99_MAJOR
,IFACE99_MINOR
,IFACE99_PATCH
,IFACE99_VERSION_COMPATIBLE(x, y, z)
, andIFACE99_VERSION_EQ(x, y, z)
have the same semantics as of Metalang99. -
For each macro using
ML99_EVAL
, Interface99 provides its Metalang99-compliant 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.)
- 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
andvfuncDefault
to theStatementMacros
vector (see our.clang-format
). It will instruct the formatter to place them onto different lines.
- 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:
// `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:
// 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);
The design of Interface99 may raise some questions. In this section, you may find answers why it was designed in this way.
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:
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.
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.
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:
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.
Thanks to Rust and Golang for their implementations of traits/interfaces.
- Comparing Golang and Interface99
- What’s the Point of the C Preprocessor, Actually?
- Macros on Steroids, Or: How Can Pure C Benefit From Metaprogramming
- Extend Your Language, Don’t Alter It
- Update
IFACE99_MAJOR
,IFACE99_MINOR
, andIFACE99_PATCH
ininterface99.h
. - Update
CHANGELOG.md
. - Release the project in GitHub Releases.
A: See Datatype99's README >>.
A: See Metalang99's README >>.
A: Interface99 is implemented upon Metalang99, a preprocessor metaprogramming library that allows enriching pure C with some custom syntax sugar.
A: Yes, C++11 and onwards is supported.
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.
[playground.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);
| ^~~~
[playground.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’)
[playground.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
[playground.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}); }
| ~~~^
| )
[playground.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.
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.
A: Interface99 is known to work on these compilers:
- GCC
- Clang
- MSVC
- TCC