{"id":13836116,"url":"https://github.com/Hirrolot/datatype99","last_synced_at":"2025-07-10T13:31:34.825Z","repository":{"id":45072098,"uuid":"312137187","full_name":"hirrolot/datatype99","owner":"hirrolot","description":"Algebraic data types for C99","archived":false,"fork":false,"pushed_at":"2025-03-17T03:16:52.000Z","size":1205,"stargazers_count":1419,"open_issues_count":0,"forks_count":23,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-06-24T06:19:45.338Z","etag":null,"topics":["adt","algebraic","algebraic-data-types","c99","derive","introspection","metalang99","metaprogramming","pattern-matching","reflection-library","sum-types","tagged-unions","type-system","variant"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hirrolot.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"buy_me_a_coffee":"hirrolot"}},"created_at":"2020-11-12T01:41:26.000Z","updated_at":"2025-06-20T22:00:08.000Z","dependencies_parsed_at":"2024-01-13T16:45:16.322Z","dependency_job_id":"9ba3faaa-540a-4d0a-93d6-af248622571b","html_url":"https://github.com/hirrolot/datatype99","commit_stats":{"total_commits":494,"total_committers":1,"mean_commits":494.0,"dds":0.0,"last_synced_commit":"7680ac6dbd5b2845d78cb5f216dfa262ea9cee0d"},"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/hirrolot/datatype99","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hirrolot%2Fdatatype99","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hirrolot%2Fdatatype99/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hirrolot%2Fdatatype99/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hirrolot%2Fdatatype99/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hirrolot","download_url":"https://codeload.github.com/hirrolot/datatype99/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hirrolot%2Fdatatype99/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264585372,"owners_count":23632646,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["adt","algebraic","algebraic-data-types","c99","derive","introspection","metalang99","metaprogramming","pattern-matching","reflection-library","sum-types","tagged-unions","type-system","variant"],"created_at":"2024-08-04T15:00:35.880Z","updated_at":"2025-07-10T13:31:34.813Z","avatar_url":"https://github.com/hirrolot.png","language":"C","funding_links":["https://buymeacoffee.com/hirrolot"],"categories":["Utilities","C","C language extensions","公用事业"],"sub_categories":["YAML"],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003ca href=\"examples/binary_tree.c\"\u003e\u003cimg src=\"images/preview.png\" width=\"600px\" /\u003e\u003c/a\u003e\n  \u003ch1\u003eDatatype99\u003c/h1\u003e\n  \u003ca href=\"https://github.com/hirrolot/datatype99/actions\"\u003e\n    \u003cimg src=\"https://github.com/hirrolot/datatype99/workflows/C/C++%20CI/badge.svg\"\u003e\n  \u003c/a\u003e\n\n  Safe, intuitive [algebraic data types] with exhaustive pattern matching \u0026 compile-time introspection facilities. No external tools required, pure C99.\n\u003c/div\u003e\n\n[algebraic data types]: https://en.wikipedia.org/wiki/Algebraic_data_type\n\n## Highlights\n\n - **Type-safe.** Such things as improperly typed variants, non-exhaustive pattern matching, and invalid field access are caught at compile-time.\n\n - **Portable.** Everything you need is a standard-conforming C99 compiler; neither the standard library, nor compiler/platform-specific functionality or VLA are required.\n\n - **Predictable.** Datatype99 comes with formal [code generation semantics], meaning that the generated data layout is guaranteed to always be the same.\n\n - **Comprehensible errors.** Datatype99 is [resilient to bad code].\n\n - **Battle-tested.** Datatype99 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.\n\n[resilient to bad code]: #q-what-about-compile-time-errors\n[OpenIPC]: https://openipc.org/\n[RTSP 1.0 implementation]: https://github.com/OpenIPC/smolrtsp/\n\n## Installation\n\nDatatype99 consists of one header file `datatype99.h` and one dependency [Metalang99]. To use it in your project, you need to:\n\n[Metalang99]: https://github.com/hirrolot/metalang99\n\n 1. Add `datatype99` and `metalang99/include` to your include directories.\n 2. Specify [`-ftrack-macro-expansion=0`] (GCC) or [`-fmacro-backtrace-limit=1`] (Clang) to avoid useless macro expansion errors.\n\n[`-ftrack-macro-expansion=0`]: https://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html\n[`-fmacro-backtrace-limit=1`]: https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-fmacro-backtrace-limit\n\nIf you use CMake, the recommended way is [`FetchContent`]:\n\n[`FetchContent`]: https://cmake.org/cmake/help/latest/module/FetchContent.html\n\n```cmake\ninclude(FetchContent)\n\nFetchContent_Declare(\n    datatype99\n    URL https://github.com/hirrolot/datatype99/archive/refs/tags/vx.y.z.tar.gz # vx.y.z\n)\n\nFetchContent_MakeAvailable(datatype99)\n\ntarget_link_libraries(MyProject datatype99)\n\n# Disable full macro expansion backtraces for Metalang99.\nif(CMAKE_C_COMPILER_ID STREQUAL \"Clang\")\n  target_compile_options(MyProject PRIVATE -fmacro-backtrace-limit=1)\nelseif(CMAKE_C_COMPILER_ID STREQUAL \"GNU\")\n  target_compile_options(MyProject PRIVATE -ftrack-macro-expansion=0)\nendif()\n```\n\n(By default, `datatype99/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.)\n\n[`FetchContent_Declare`]: https://cmake.org/cmake/help/latest/module/FetchContent.html#command:fetchcontent_declare\n\nOptionally, you can [precompile headers] in your project that rely on Datatype99. This will decrease compilation time, because the headers will not be compiled each time they are included.\n\n[precompile headers]: https://en.wikipedia.org/wiki/Precompiled_header\n\nHappy hacking!\n\n## Usage\n\nPut simply, Datatype99 is just a syntax sugar over [tagged unions]; the only difference is that it is more safe and concise. For example, to represent a binary tree, you would normally write something like this:\n\n```c\ntypedef struct {\n    struct BinaryTree *lhs;\n    int x;\n    struct BinaryTree *rhs;\n} BinaryTreeNode;\n\ntypedef struct {\n    enum { Leaf, Node } tag;\n    union {\n        int leaf;\n        BinaryTreeNode node;\n    } data;\n} BinaryTree;\n```\n\nTo avoid this boilerplate, you can use Datatype99:\n\n```c\ndatatype(\n    BinaryTree,\n    (Leaf, int),\n    (Node, BinaryTree *, int, BinaryTree *)\n);\n```\n\nSay you want to sum all nodes and leafs in your binary tree. Then you may write something like this:\n\n```c\nint sum(const BinaryTree *tree) {\n    switch (tree-\u003etag) {\n    case Leaf:\n        return tree-\u003edata.leaf;\n    case Node:\n        return sum(tree-\u003edata.node.lhs) + tree-\u003edata.node.x + sum(tree-\u003edata.node.rhs);\n    }\n\n    // Invalid input (no such variant).\n    return -1;\n}\n```\n\n... but what if you accidentally access `tree-\u003edata.node` after `case Leaf:`? Your compiler would not warn you, thus resulting in a business logic bug.\n\nWith Datatype99, you can rewrite `sum` as follows, using a technique called _pattern matching_:\n\n```c\nint sum(const BinaryTree *tree) {\n    match(*tree) {\n        of(Leaf, x) return *x;\n        of(Node, lhs, x, rhs) return sum(*lhs) + *x + sum(*rhs);\n    }\n\n    // Invalid input (no such variant).\n    return -1;\n}\n```\n\n`of` gives you variables called _bindings_: `x`, `lhs`, or `rhs`. This design has a few neat aspects:\n\n - **Compile-time safety.** The bindings of `Node` are invisible after `of(Leaf, x)` and vice versa, so compilation will fail to proceed if you access them inappropriately.\n - **Flexibility.** Bindings have pointer types so that you can mutate them, thereby mutating the whole `tree`; in order to obtain a value, you can dereference them, as shown in the example: `return *x;`.\n\nThe last thing unmentioned is how you construct variants. Internally, Datatype99 generates `inline static` functions called _value constructors_; you can use them as follows:\n\n```c\nBinaryTree leaf5 = Leaf(5);\nBinaryTree leaf7 = Leaf(7);\nBinaryTree node = Node(\u0026leaf5, 123, \u0026leaf7);\n```\n\nFinally, just a few brief notes about pattern matching:\n\n - To match the default case, write `otherwise { ... }` at the end of `match`.\n - To ignore a binding, write `_`: `of(Foo, a, b, _, d)`.\n - Please, [**do not use top-level `break`/`continue`**](#top-level-breakcontinue) inside statements provided to `of` and `ifLet`; use `goto` labels instead.\n\nCongratulations, this is all you need to know to write most of the stuff! If you feel fancy, you can also introspect your types at compile-time; see [`examples/derive/`](examples/derive/) for the examples.\n\n[tagged unions]: https://en.wikipedia.org/wiki/Tagged_union\n\n## Syntax and semantics\n\nHaving a well-defined semantics of the macros, you can write an FFI which is quite common in C.\n\n### EBNF syntax\n\n```ebnf\n\u003cdatatype\u003e      ::= \"datatype(\" [ \u003cderive-clause\u003e \",\" ] \u003cdatatype-name\u003e { \",\" \u003cvariant\u003e }+ \")\" ;\n\u003crecord\u003e        ::= \"record(\"   [ \u003cderive-clause\u003e \",\" ] \u003crecord-name\u003e   { \",\" \u003cfield\u003e   }* \")\" ;\n\u003cdatatype-name\u003e ::= \u003cident\u003e ;\n\u003crecord-name\u003e   ::= \u003cident\u003e ;\n\n\u003cvariant\u003e       ::= \"(\" \u003cvariant-name\u003e { \",\" \u003ctype\u003e }* \")\" ;\n\u003cfield\u003e         ::= \"(\" \u003ctype\u003e \",\" \u003cfield-name\u003e \")\" ;\n\u003cvariant-name\u003e  ::= \u003cident\u003e ;\n\u003cfield-name\u003e    ::= \u003cident\u003e ;\n\n\u003cderive-clause\u003e ::= \"derive(\" \u003cderiver-name\u003e { \",\" \u003cderiver-name\u003e }* \")\" ;\n\u003cderiver-name\u003e  ::= \u003cident\u003e ;\n\n\u003cmatch\u003e         ::= \"match(\" \u003clvalue\u003e \") {\" { \u003cof\u003e }* [ \u003cotherwise\u003e ] \"}\" ;\n\u003cmatches\u003e       ::= \"MATCHES(\" \u003cexpr\u003e \",\" \u003cident\u003e \")\" ;\n\u003cif-let\u003e        ::= \"ifLet(\" \u003clvalue\u003e \",\" \u003cvariant-name\u003e \",\" \u003cident\u003e { \",\" \u003cident\u003e }* \")\" \u003cstmt\u003e ;\n\u003cof\u003e            ::= \"of(\" \u003cvariant-name\u003e { \",\" \u003cident\u003e }* \")\" \u003cstmt\u003e ;\n\u003cotherwise\u003e     ::= \"otherwise\" \u003cstmt\u003e ;\n```\n\n\u003cdetails\u003e\n  \u003csummary\u003eNote: shortened vs. postfixed versions\u003c/summary\u003e\n\n  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_ (`match99`, `of99`, `derive99`, etc.), which are defined unconditionally. If you want to avoid name clashes caused by shortened versions, define `DATATYPE99_NO_ALIASES` before including `datatype99.h`. Library headers are strongly advised to use the postfixed macros, but without resorting to `DATATYPE99_NO_ALIASES`.\n\u003c/details\u003e\n  \n### Semantics\n\n(It might be helpful to look at the [generated data layout](https://godbolt.org/z/rebxMxW43) of [`examples/binary_tree.c`](examples/binary_tree.c).)\n\n#### `datatype`\n\n 1. Before everything, the following type definition is generated:\n\n```\ntypedef struct \u003cdatatype-name\u003e \u003cdatatype-name\u003e;\n```\n\n 2. For each non-empty variant, the following type definition is generated (the metavariable `\u003ctype\u003e` ranges over a corresponding variant's types):\n\n```\ntypedef struct \u003cdatatype-name\u003e\u003cvariant-name\u003e {\n    \u003ctype\u003e0 _0;\n    ...\n    \u003ctype\u003eN _N;\n} \u003cdatatype-name\u003e\u003cvariant-name\u003e;\n```\n\n 3. For each non-empty variant, the following type definitions to types of each field of `\u003cdatatype-name\u003e\u003cvariant-name\u003e` are generated:\n\n```\ntypedef \u003ctype\u003e0 \u003cvariant-name\u003e_0;\n...\ntypedef \u003ctype\u003eN \u003cvariant-name\u003e_N;\n```\n\n 4. For each variant, the following type definition to a corresponding sum type is generated:\n\n```\ntypedef struct \u003cdatatype-name\u003e \u003cvariant-name\u003eSumT;\n```\n\n 5. For each sum type, the following tagged union is generated (inside the union, only fields to structures of non-empty variants are generated):\n\n```\ntypedef enum \u003cdatatype-name\u003eTag {\n    \u003cvariant-name\u003e0Tag, ..., \u003cvariant-name\u003eNTag\n} \u003cdatatype-name\u003eTag;\n\ntypedef union \u003cdatatype-name\u003eVariants {\n    char dummy;\n\n    \u003cdatatype-name\u003e\u003cvariant-name\u003e0 \u003cvariant-name\u003e0;\n    ...\n    \u003cdatatype-name\u003e\u003cvariant-name\u003eN \u003cvariant-name\u003eN;\n} \u003cdatatype-name\u003eVariants;\n\nstruct \u003cdatatype-name\u003e {\n    \u003cdatatype-name\u003eTag tag;\n    \u003cdatatype-name\u003eVariants data;\n};\n```\n\n\u003cdetails\u003e\n  \u003csummary\u003eNote on char dummy;\u003c/summary\u003e\n\n`char dummy;` is needed to make the union contain at least one item, according to the standard, even if all variants are empty. Such a `datatype` would enforce strict type checking unlike plain C `enum`s.\n\u003c/details\u003e\n\n 6. For each variant, the following function called a _value constructor_ is generated:\n\n```\ninline static \u003cdatatype-name\u003e \u003cvariant-name\u003e(/* ... */) { /* ... */ }\n```\n\nIf the variant has no parameters, this function will take `void` and initialise `.data.dummy` to `'\\0'`; otherwise, it will take the corresponding variant parameters and initialise the result value as expected.\n\n 7. Now, when a sum type is fully generated, the derivation process takes place. Each deriver taken from `derive(...)` is invoked sequentially, from left to right, as\n\n```\nML99_call(DATATYPE99_DERIVE_##\u003cderiver-name\u003eI, v(\u003cdatatype-name\u003e), variants...)\n```\n\nwhere\n - `\u003cderiver-name\u003eI` corresponds to a [Metalang99-compliant] macro of the form `#define DATATYPE99_DERIVE_##\u003cderiver-name\u003eI_IMPL(name, variants) /* ... */`.\n - `variants...` is a [list] of variants represented as two-place [tuples]: `(\u003cvariant-name\u003e, types...)`, where\n   - `types...` is a [list] of types of the corresponding variant.\n\nPut simply, a deriver is meant to generate something global for a sum type, like interface implementations or almost any other stuff. In terms of Rust, you can think of it as of the [`derive` attribute].\n\n[list]: https://metalang99.readthedocs.io/en/latest/list.html\n[tuples]: https://metalang99.readthedocs.io/en/latest/tuple.html\n[`derive` attribute]: https://doc.rust-lang.org/reference/attributes/derive.html\n\n#### `record`\n\n`record` represents a _record type_: it is simply a `struct` for which the derivation process is defined.\n\n 1. The following structure is generated:\n\n```\ntypedef struct \u003crecord-name\u003e {\n    // Only if \u003crecord-name\u003e has no fields:\n    char dummy;\n\n    \u003ctype\u003e0 \u003cfield-name\u003e0;\n    ...\n    \u003ctype\u003eN \u003cfield-name\u003eN;\n} \u003crecord-name\u003e;\n```\n\n\u003cdetails\u003e\n  \u003csummary\u003eNote on char dummy;\u003c/summary\u003e\n\n`char dummy;` is needed to make the structure contain at least one item, according to the standard. Such `record(Foo)` can be used to implement interfaces for it (see [Interface99]).\n\u003c/details\u003e\n\n[Interface99]: https://github.com/hirrolot/interface99\n\n 2. Each deriver taken from `derive(...)` is invoked sequentially, from left to right, as\n\n```\nML99_call(DATATYPE99_RECORD_DERIVE_##\u003cderiver-name\u003eI, v(\u003crecord-name\u003e), fields...)\n```\n\nwhere\n - `\u003cderiver-name\u003eI` corresponds to a [Metalang99-compliant] macro of the form `#define DATATYPE99_RECORD_DERIVE_##\u003cderiver-name\u003eI_IMPL(name, fields) /* ... */`.\n - `fields...` is a [list] of fields represented as two-place [tuples]: `(\u003ctype\u003e, \u003cfield-name\u003e)`. If a record contains no fields, the list would consist only of `(char, dummy)`.\n\n#### `match`\n\n`match` has the expected semantics: it sequentially tries to match the given instance of a sum type against the given variants, and, if a match has succeeded, it executes the corresponding statement and moves down to the next instruction (`match(val) { ... } next-instruction;`). If all the matches have failed, it executes the statement after `otherwise` and moves down to the next instruction.\n\nA complete `match` construct results in a single C statement.\n\n#### `of`\n\n`of` accepts a matched variant name as a first argument and the rest of arguments comprise a comma-separated list of bindings.\n\n - A binding equal to `_` is ignored.\n - A binding **not** equal to `_` stands for a pointer to a corresponding data of the variant (e.g., let there be `(Foo, T1, T2)` and `of(Foo, x, y)`, then `x` has the type `T1 *` and `y` is `T2 *`).\n\nThere can be more than one `_` binding, however, non-`_` bindings must be distinct.\n\nTo match an empty variant, write `of(Bar)`.\n\n#### `MATCHES`\n\n`MATCHES` just tests an instance of a sum type for a given variant. If the given instance corresponds to the given variant, it expands to truthfulness, otherwise it expands to falsehood.\n\n#### `matches`\n\n**DEPRECATED**: use [`MATCHES`](#MATCHES) instead.\n\n#### `ifLet`\n\n`ifLet` tries to match the given instance of a sum type against the given variant, and, if a match has succeeded, it executes the corresponding statement.\n\nThink of `ifLet(\u003cexpr\u003e, \u003cvariant-name\u003e, vars...) { /* ... */ }` as of an abbreviation of\n\n```\nmatch(\u003cexpr\u003e) {\n    of(\u003cvariant-name\u003e, vars...) { /* ... */ }\n    otherwise {}\n}\n```\n\nA complete `ifLet` construct results in a single C statement.\n\n## Unit type\n\nThe unit type `UnitT99` represents the type of a single value, `unit_v99` (it should not be assigned to anything else). These are defined as follows:\n\n```c\ntypedef char UnitT99;\nstatic const UnitT99 unit_v99 = '\\0';\n```\n\nIf `DATATYPE99_NO_ALIASES` remains undefined prior to `#include \u003cdatatype99.h\u003e`, `UnitT99` and `unit_v99` are also accessible through object-like macros `UnitT` \u0026 `unit_v`.\n\n## Derive helper attributes\n\nYou can pass named arguments to a deriver; these are called _derive helper attributes_. They must be specified as object-like macros of the form:\n\n```\n#define \u003cvariant-name\u003e_\u003cnamespace\u003e_\u003cattribute-name\u003e attr(/* attribute value */)\n```\n\nwhere `\u003cnamespace\u003e` is either `\u003cdatatype-name\u003e`/`\u003crecord-name\u003e` or `\u003cvariant-name\u003e`/`\u003cfield-name\u003e` for `datatype`/`record`-specific and variant/field-specific attributes, respectively.\n\nTo manipulate derive helper attributes, there are a few predefined macros:\n\n - `DATATYPE99_attrIsPresent`/`DATATYPE99_ATTR_IS_PRESENT`\n\n    Accepts an attribute name and checks if it is present or not. It can be used to check the presence of an optional attribute.\n\n - `DATATYPE99_attrValue`/`DATATYPE99_ATTR_VALUE`\n\n    Accepts an attribute name extracts its value. A provided attribute **must** be present.\n\n - `DATATYPE99_assertAttrIsPresent`\n\n    Accepts an attribute name and emits a fatal error if the attribute is not present, otherwise results in emptiness. It can be used for mandatory attributes.\n\n(The naming convention here is the same [as of Metalang99](https://metalang99.readthedocs.io/en/latest/#naming-conventions).)\n\n## Miscellaneous\n\n - The macros `DATATYPE99_MAJOR`, `DATATYPE99_MINOR`, `DATATYPE99_PATCH`, `DATATYPE99_VERSION_COMPATIBLE(x, y, z)`, and `DATATYPE99_VERSION_EQ(x, y, z)` have the [same semantics as of Metalang99](https://metalang99.readthedocs.io/en/latest/#version-manipulation-macros).\n\n - For each macro using `ML99_EVAL`, Datatype99 provides its [Metalang99-compliant] counterpart which can be used inside derivers and other Metalang99-compliant macros:\n\n| Macro | Metalang99-compliant counterpart |\n|----------|----------|\n| `datatype` | `DATATYPE99_datatype` |\n| `record` | `DATATYPE99_record` |\n| `of` | `DATATYPE99_of` |\n| `ifLet` | `DATATYPE99_ifLet` |\n\n(An [arity specifier] and [desugaring macro] are provided for each of the above macros.)\n\n - There is a built-in deriver `dummy` which generates nothing. It is defined both for record and sum types.\n\n[SemVer]: https://semver.org\n[Metalang99-compliant]: https://metalang99.readthedocs.io/en/latest/#definitions\n[arity specifier]: https://hirrolot.gitbook.io/metalang99/partial-application\n[desugaring macro]: https://metalang99.readthedocs.io/en/latest/#definitions\n\n## Guidelines\n\n### Clang-Format issues\n\nIf you use [Clang-Format], cancel formatting for a `datatype` definition using `// clang-format off` \u0026 `// clang-format on` to make it look prettier, as in the examples.\n\n### `#undef` derive helper attributes\n\nAlways `#undef` derive helper attributes after a corresponding `datatype` definition not to pollute your namespace.\n\n### Descriptive names\n\nIf the meaning of variant parameters is not clear from the context, give them descriptive names. This can be achieved in several ways:\n\n```c\n// 1. Define type aliases to variant parameters.\ntypedef double XCoordinate;\ntypedef double YCoordinate;\n\ntypedef double Width;\ntypedef double Height;\n\ndatatype(\n    Shape,\n    (Point, XCoordinate, YCoordinate),\n    (Rectangle, Width, Height)\n);\n\n// 2. Define separate structures.\ntypedef struct {\n    double x, y;\n} Point;\n\ntypedef struct {\n    double width, height;\n} Rectangle;\n\ndatatype(\n    Shape,\n    (MkPoint, Point),\n    (MkRectangle, Rectangle)\n);\n```\n\nComparison:\n\n - The former option has more concise syntax: `MkPoint(x, y)` instead of `MkPoint((Point){x, y})`.\n - The latter option is more appropriate when the structures are to be used separately from the containing sum type.\n - The latter option allows for more graduate control over the data layout: you can accompain the structures with compiler-specific attributes, alignment properties like `__attribute__ ((__packed__))`, etc.\n\n[Clang-Format]: https://clang.llvm.org/docs/ClangFormatStyleOptions.html\n\n## Pitfalls\n\n### Top-level `break`/`continue`\n\nDo **not** use `break`/`continue` inside a statement provided to `of`/`ifLet` but outside of any `for`/`while` loops in that statement. For example, this code is fine:\n\n```c\nmatch(x) {\n    of(Foo, a, b, c) {\n        for (int i = 0; i \u003c 10; i++) {\n            continue;\n        }\n    }\n}\n```\n\nBut this code is **not** fine:\n\n```c\nfor (int i = 0; i \u003c 10; i++) {\n    match(x) {\n        of(Foo, a, b, c) {\n            if (a == 7) { break; }\n            continue;\n        }\n    }\n}\n```\n\nTo make it valid, you can rewrite it as follows:\n\n```c\nfor (int i = 0; i \u003c 10; i++) {\n    match(x) {\n        of(Foo, a, b, c) {\n            if (a == 7) { goto my_break; }\n            goto my_continue;\n        }\n    }\n\n    // Datatype99 prohibits top-level `break`/`continue`.\n    my_continue:;\n}\nmy_break:;\n```\n\n### Array as a variant parameter\n\nTo specify an array as a variant parameter, you must put it into a separate `struct`; see [`examples/array_in_variant.c`](examples/array_in_variant.c).\n\n### Mutable bindings\n\nBindings introduced by `of` are **always** mutable, so make sure you do **not** mutate them if the value passed to `match` is qualified as `const`.\n\n## Credits\n\nThanks to Rust and ML for their implementations of sum types.\n\n## Blog posts\n\n - [_Pretty-Printable Enumerations in Pure C_](https://hirrolot.github.io/posts/pretty-printable-enumerations-in-pure-c.html)\n - [_What’s the Point of the C Preprocessor, Actually?_](https://hirrolot.github.io/posts/whats-the-point-of-the-c-preprocessor-actually.html)\n - [_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)\n - [_Extend Your Language, Don’t Alter It_](https://hirrolot.github.io/posts/extend-your-language-dont-alter-it.html)\n - [_Compiling Algebraic Data Types in Pure C99_]\n - [_Comparing Rust and Datatype99_](https://www.reddit.com/r/ProgrammingLanguages/comments/nc1o18/comparing_algebraic_data_types_rust_and_datatype99/)\n - [_Compile-Time Introspection of Sum Types in Pure C99_](https://hirrolot.github.io/posts/compile-time-introspection-of-sum-types-in-pure-c99.html)\n - [_Unleashing Sum Types in Pure C99_](https://hirrolot.github.io/posts/unleashing-sum-types-in-pure-c99.html)\n\n[_Compiling Algebraic Data Types in Pure C99_]: https://hirrolot.github.io/posts/compiling-algebraic-data-types-in-pure-c99.html\n\n## Release procedure\n\n 1. Update `DATATYPE99_MAJOR`, `DATATYPE99_MINOR`, and `DATATYPE99_PATCH` in `datatype99.h`.\n 2. Update `CHANGELOG.md`.\n 3. Release the project in [GitHub Releases].\n\n[GitHub Releases]: https://github.com/hirrolot/datatype99/releases\n\n## FAQ\n\n### Q: Why use C instead of Rust/Zig/whatever else?\n\nA: There is a lot of software written in plain C that can benefit from Datatype99; C is #1 programming language as of 2020, [according to TIOBE](https://jaxenter.com/c-programming-may-2020-171598.html). People use C due to technical and social reasons:\n\n - Datatype99 can be seamlessly integrated into existing codebases written in pure C -- just `#include \u003cdatatype99.h\u003e` and you are ready to go. On the other hand, other languages force you to separate native C files from their sources, which is clearly less convenient.\n\n - In some environments, developers strick to pure C for historical reasons (e.g., embedded devices, Linux and other operating systems).\n\n - C has a stable ABI which is vital for some projects (e.g., plugin systems such as [MetaCall]).\n\n - C is a mature language with a complete specification and a plenitude of libraries. Rust has no complete specification, and [Zig] is not yet production-ready. I know a few stories when these two languages were rejected for new projects, and I can understand this decision.\n\n - Historically, C has been targeting nearly all platforms. This is not the case with Rust, which depends on LLVM as for now.\n\n - Your company obligates you to use C.\n\n - Etc.\n\n[MetaCall]: https://github.com/metacall/core\n[Zig]: https://ziglang.org/\n\nSee also:\n - [_\"Rust is not a good C replacement\"_](https://drewdevault.com/2019/03/25/Rust-is-not-a-good-C-replacement.html) by Drew DeVault.\n\nOverall, if you can afford a more modern/high-level language, I encourage you to do so instead of using old C. However, many people do not have this possibility (or it would be too costly).\n\n### Q: Why not third-party code generators?\n\nA: See [Metalang99's README \u003e\u003e](https://github.com/hirrolot/metalang99#q-why-not-third-party-code-generators).\n\n### Q: How does it work?\n\nA: In short, `datatype` expands to a tagged union with value constructors; `match` expands to a switch statement. To generate all this stuff, [Metalang99] is used, a preprocessor metaprogramming library.\n\nMore on it in [_\"Compiling Algebraic Data Types in Pure C99\"_](https://hirrolot.github.io/posts/compiling-algebraic-data-types-in-pure-c99.html).\n\n### Q: Does it work on C++?\n\nA: Yes, C++11 and onwards is supported.\n\n### Q: What is the difference between Datatype99 and Metalang99?\n\nA: [Metalang99] is a functional language for metaprogramming, whereas Datatype99 is an implementation of algebraic data types written in this language.\n\n### Q: What about compile-time errors?\n\nA: Some kinds of syntactic errors are detected by the library itself:\n\n#### Error: `Bar(int)` instead of `(Bar, int)`\n\n[`playground.c`]\n```c\ndatatype(A, (Foo, int), Bar(int));\n```\n\n[`/bin/sh`]\n```\n$ gcc playground.c -Imetalang99/include -Idatatype99 -ftrack-macro-expansion=0\nplayground.c:3:1: error: static assertion failed: \"ML99_assertIsTuple: Bar(int) must be (x1, ..., xN)\"\n    3 | datatype(A, (Foo, int), Bar(int));\n      | ^~~~~~~~\n```\n\n----------\n\n#### Error: Missing comma\n\n[`playground.c`]\n```c\ndatatype(A, (Foo, int) (Bar, int));\n```\n\n[`/bin/sh`]\n```\n$ gcc playground.c -Imetalang99/include -Idatatype99 -ftrack-macro-expansion=0\nplayground.c:3:1: error: static assertion failed: \"ML99_assertIsTuple: (Foo, int) (Bar, int) must be (x1, ..., xN), did you miss a comma?\"\n    3 | datatype(A, (Foo, int) (Bar, int));\n      | ^~~~~~~~\n```\n\n----------\n\n#### Error: Trailing comma is prohibited\n\n[`playground.c`]\n```c\ndatatype(A, (Foo, int), (Bar, int), /* trailing comma is prohibited */);\n```\n\n[`/bin/sh`]\n```\n$ gcc playground.c -Imetalang99/include -Idatatype99 -ftrack-macro-expansion=0\nplayground.c:3:1: error: static assertion failed: \"ML99_assertIsTuple: must be (x1, ..., xN)\"\n    3 | datatype(A, (Foo, int), (Bar, int), /* trailing comma is prohibited */);\n      | ^~~~~~~~\n```\n\n(For better diagnostics, use the latest Metalang99.)\n\nThe others are understandable as well:\n\n#### Error: unknown type name specified in `datatype`\n\n[`playground.c`]\n```c\ndatatype(Foo, (FooA, NonExistingType));\n```\n\n[`/bin/sh`]\n```\nplayground.c:3:1: error: unknown type name ‘NonExistingType’\n    3 | datatype(\n      | ^~~~~~~~\nplayground.c:3:1: error: unknown type name ‘NonExistingType’\nplayground.c:3:1: error: unknown type name ‘NonExistingType’\n```\n\n----------\n\n#### Error: non-exhaustive `match`\n\n[`playground.c`]\n```c\nmatch(*tree) {\n    of(Leaf, x) return *x;\n    // of(Node, lhs, x, rhs) return sum(*lhs) + *x + sum(*rhs);\n}\n```\n\n[`/bin/sh`]\n```\nplayground.c: In function ‘sum’:\nplayground.c:6:5: warning: enumeration value ‘NodeTag’ not handled in switch [-Wswitch]\n    6 |     match(*tree) {\n      |     ^~~~~\n```\n\n----------\n\n#### Error: excess binders in `of`\n\n[`playground.c`]\n```c\nmatch(*tree) {\n    of(Leaf, x, excess) return *x;\n    of(Node, lhs, x, rhs) return sum(*lhs) + *x + sum(*rhs);\n}\n```\n\n[`/bin/sh`]\n```\nplayground.c: In function ‘sum’:\nplayground.c:15:9: error: unknown type name ‘Leaf_1’; did you mean ‘Leaf_0’?\n   15 |         of(Leaf, x, excess) return *x;\n      |         ^~\n      |         Leaf_0\nplayground.c:15:9: error: ‘BinaryTreeLeaf’ has no member named ‘_1’; did you mean ‘_0’?\n   15 |         of(Leaf, x, excess) return *x;\n      |         ^~\n      |         _0\n```\n\n----------\n\n#### Error: improperly typed variant arguments\n\n[`playground.c`]\n```c\nBinaryTree tree = Leaf(\"hello world\");\n```\n\n[`/bin/sh`]\n```\nplayground.c: In function ‘main’:\nplayground.c:18:28: warning: passing argument 1 of ‘Leaf’ makes integer from pointer without a cast [-Wint-conversion]\n   18 |     BinaryTree tree = Leaf(\"hello world\");\n      |                            ^~~~~~~~~~~~~\n      |                            |\n      |                            char *\nplayground.c:6:1: note: expected ‘int’ but argument is of type ‘char *’\n    6 | datatype(\n      | ^~~~~~~~\n```\n\n----------\n\n#### Error: an undereferenced binder\n\n[`playground.c`]\n```c\nint sum(const BinaryTree *tree) {\n    match(*tree) {\n        of(Leaf, x) return x; // x is int *\n        of(Node, lhs, x, rhs) return sum(*lhs) + *x + sum(*rhs);\n    }\n}\n```\n\n[`/bin/sh`]\n```\nplayground.c: In function ‘sum’:\nplayground.c:17:28: warning: returning ‘Leaf_0 *’ {aka ‘int *’} from a function with return type ‘int’ makes integer from pointer without a cast [-Wint-conversion]\n   17 |         of(Leaf, x) return x; // x is int *\n      |                            ^\n```\n\n----------\n\nFrom my experience, nearly 95% of errors make sense.\n\nIf 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.\n\n[code generation semantics]: #semantics\n\n### Q: What about IDE support?\n\n\u003cimg src=\"images/suggestion.png\" width=\"600px\" /\u003e\n\nA: VS Code automatically enables suggestions of generated types but, of course, it does not support macro syntax highlighting.\n\n### Q: Which compilers are tested?\n\nA: Datatype99 is known to work on these compilers:\n\n - GCC\n - Clang\n - MSVC\n - TCC\n\n## Troubleshooting\n\n### `warning: control reaches end of non-void function [-Wreturn-type]`\n\nThis warning happens when you try to return control from within a `match` statement, and your compiler thinks that not all hypothetical variants are handled. For example:\n\n```c\ndatatype(MyType, (Foo), (Bar));\n\nint handle(MyType val) {\n    match(val) {\n        of(Foo) return 5;\n        of(Bar) return 7;\n    }\n}\n```\n\nThe above code may seem perfect at first glance, but in fact, it is not. The reason is this: `match(val)` boils down to `switch(val.tag)` under the hood, with `val.tag` being an ordinary C enumeration consisting of the variants `Foo` and `Bar`. But what if a caller provides us with neither `Foo` nor `Bar`, but with something like `42` (not a valid variant)? Since `enum` is merely another way to give integers names, a compiler would not complain on the _caller_ site. However, on the _callee_ site, we would have the warning:\n\n```\ntest.c: In function ‘handle’:\ntest.c:10:1: warning: control reaches end of non-void function [-Wreturn-type]\n   10 | }\n      | ^\n```\n\nThe solution is to either panic or return some error-signaling code, like this:\n\n```c\nint handle(MyType val) {\n    match(val) {\n        of(Foo) return 5;\n        of(Bar) return 7;\n    }\n\n    // Invalid input (no such variant).\n    return -1;\n}\n```\n\nSee [issue #9](https://github.com/hirrolot/datatype99/issues/9).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FHirrolot%2Fdatatype99","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FHirrolot%2Fdatatype99","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FHirrolot%2Fdatatype99/lists"}