Notes for C


1. Functions are named "Az" + class name + "_" + function name in pascalCase. Modules / namespaces do not exist:

app::App::new() = AzApp_new()

2. Enums are named "Az" + enum name + "_" + variant name:

LayoutAlignItems::Stretch = AzLayoutAlignItems_Stretch

3. union enums have special compile-time macros that crate the correct union variant + tag at compile time:

AzStyleCursorValue cursor = AzStyleCursorValue_Exact(AzStyleCursor_Grab);

4. If a class is marked with has destructor, it has a corresponding _delete() function. The destructors automatically call the sub-destructors of all fields (i.e. you do not need to recurse and delete every field manually).

# App is marked as "has_destructor": AzApp app = AzApp_new(/* ... */); # constructor AzApp_delete(&app); # destructor # value of app is undefined here

5. All classes can be deep-copied via _deepCopy() - note that this might be very expensive for large objects.

AzWindowCreateOptions w = AzWindowCreateOptions_new(); AzWindowCreateOptions copy = AzWindowCreateOptions_copy(&w);

6. To emulate pattern matching in C, every union enum has a corresponding $union_matchRef$variant() and $union_matchMut$variant() function. Both take a pointer to the union and an (uninitialized) pointer to the output. If the union is of variant $variant, the output pointer will be initialized:

// create a union enum AzStyleCursorValue cursor = AzStyleCursorValue_Exact(AzStyleCursor_Grab); // destructure a union enum AzStyleCursor* result; if AzStyleCursorValue_matchRefExact(&cursor, &result) { printf("ok!\n"); // result is initialized here }
The difference between _matchRef() and _matchMut() is that takes a const * and _matchMut() takes a restrict * to the result. In the example, the lifetime of result is equal to the lifetime of cursor (since result simply points to the payload of the tagged cursor union).

7. Run-time type reflection for RefAny can implemented via the AZ_REFLECT macro:

typedef struct { int field; } MyStruct; void MyStruct_delete(MyStruct* restrict A) { } // destructor AZ_REFLECT(MyStruct, MyStruct_delete);

The AZ_REFLECT macro generates functions to upcast from your struct to a RefAny as well as to do a checked downcast from a RefAny to your custom data type:

// create a new RefAny AzRefAny object = MyStruct_upcast(MyStruct { .field = 5 }); // Creates an uninitialized borrow and downcast it // fails if "object" is already borrowed mutably // or if the "RefAny" is not of type "MyStruct" (type check at runtime) MyStructRef structref = MyStructRef_create(&object); if MyStruct_downcastRef(&object, &structref) { printf!("ok! - %d", structref->ptr.field); } // unlocks "object" to be borrowed mutably again MyStructRef_delete(&structref); // Creates a borrow that can MODIFY the contents of the RefAny // (mutable "* restrict" borrow instead of "const*" borrow) // // The object cannot be borrowed referentially and mutably at // the same time - this invariant is checked at runtime. MyStructRefMut structrefmut = MyStructRefMut_create(&object); if MyStruct_downcastRefMut(&object, &structrefmut) { // structrefmut can modify the contents of the RefAny structrefmut->ptr.field = 6; // prints "6", value is now changed, visible to all copies of "object" printf!("ok! - %d", structref->ptr.field); } MyStructRefMut_delete(&structrefmut); // decreases the refcount - if refcount is 0, destructor is called if !MyStructRefAny_delete(&object) { /* RefAny is not of type "MyStruct" */ }

8. All *Vec data types have a _empty() constructor macro (to create an empty vec at compile time). The AzString type has an extra _fromConstStr() macro to define compile-time strings:

AzScanCodeVec v = AzScanCodeVec_empty(); AzString s = AzString_fromConstStr("hello"); // evaluated at compile-time