Pronounced "See-yan" because I'm a monster
A header-only C11 library that brings modern programming paradigms to C, including Option/Result types, functional primitives, smart pointers, coroutines, channels, and more.
| Feature | Description |
|---|---|
| Primitive Type Aliases | Concise, predictable-size type names (i32, u64, f32, etc.) |
| Option Type | Explicit nullable value handling |
| Result Type | Explicit error handling without errno |
| Vector | Generic dynamic arrays with bounds checking |
| Slice | Safe array views with bounds information |
| HashMap | Type-safe hash maps with O(1) lookups |
| String | Dynamic strings with safe operations |
| Functional Primitives | map, filter, reduce, foreach |
| Smart Pointers | Unique and shared pointers with automatic cleanup |
| Defer | Scope-based resource cleanup (RAII-style) |
| Coroutines | Stackful cooperative multitasking |
| Channels | CSP-style communication primitives |
| Pattern Matching | Ergonomic Option/Result handling |
| Serialization | Text-based data serialization with S-expression format |
| Custom Bit-Width Integers | Zig-inspired integers with arbitrary bit widths (u6, i12, etc.) |
| Bitset | Fixed-size bit collections for efficient flag management |
Copy the include/cyan/ directory to your project and include the headers:
#include <cyan/cyan.h> // Include everything
// Or include individual headers:
#include <cyan/option.h>
#include <cyan/vector.h>Concise type names with predictable sizes, inspired by Rust and Zig.
#include <cyan/common.h>
// Fixed-width signed integers
i8 a = 127; // int8_t
i16 b = 32767; // int16_t
i32 c = 2147483647; // int32_t
i64 d = 9223372036854775807LL; // int64_t
// Fixed-width unsigned integers
u8 e = 255; // uint8_t
u16 f = 65535; // uint16_t
u32 g = 4294967295U; // uint32_t
u64 h = 18446744073709551615ULL; // uint64_t
// Pointer-sized integers
usize len = sizeof(array) / sizeof(array[0]); // size_t compatible
isize offset = -100; // signed pointer-sized
// Floating-point
f32 pi_f = 3.14159f; // float
f64 pi_d = 3.14159265358979; // double
// Type-erased pointer
any* generic_ptr = &some_data;Available Types:
| Category | Types |
|---|---|
| Signed integers | i8, i16, i32, i64, i128* |
| Unsigned integers | u8, u16, u32, u64, u128* |
| Pointer-sized | isize, usize |
| Floating-point | f16, f32, f64, f80, f128* |
| Special | bool, any |
*Platform-dependent. Check CYAN_HAS_INT128, CYAN_HAS_FLOAT16, CYAN_HAS_FLOAT80, CYAN_HAS_FLOAT128 macros.
Zig-inspired integer types with arbitrary bit widths (1-64 bits). Define custom unsigned integers with UINT_DEFINE(N) and signed integers with INT_DEFINE(N).
#include <cyan/bitint.h>
// Define custom bit-width types
UINT_DEFINE(6); // u6: 6-bit unsigned (0-63)
UINT_DEFINE(12); // u12: 12-bit unsigned (0-4095)
INT_DEFINE(6); // i6: 6-bit signed (-32 to 31)
INT_DEFINE(12); // i12: 12-bit signed (-2048 to 2047)
i32 main(void) {
// Unsigned integers - values are masked to fit
u6 val = u6_new(42); // 42
u6 overflow = u6_new(100); // 36 (100 & 0x3F)
printf("Value: %u\n", u6_get(&val));
// Arithmetic operations (results masked to N bits)
u6 a = u6_new(30);
u6 b = u6_new(40);
u6 sum = u6_add(a, b); // 6 (70 wraps at 64)
// Bitwise operations
u6 masked = u6_and(a, b);
u6 shifted = u6_shl(a, 2);
// Signed integers with sign extension
i6 pos = i6_new(20);
i6 neg = i6_new(-15);
i6 diff = i6_sub(pos, neg); // Wraps in 6-bit signed range
i6 negated = i6_neg(pos); // -20
// Min/max values
u6 max_u6 = u6_max(); // 63
i6 min_i6 = i6_min(); // -32
return 0;
}Unsigned Integer API (UINT_DEFINE):
| Function | Description |
|---|---|
uN_new(value) |
Create N-bit unsigned integer (value masked to N bits) |
uN_get(ptr) |
Get value as backing type |
uN_raw(ptr) |
Get raw backing value |
uN_add(a, b) |
Add two values (result masked) |
uN_sub(a, b) |
Subtract two values (result masked) |
uN_mul(a, b) |
Multiply two values (result masked) |
uN_and(a, b) |
Bitwise AND |
uN_or(a, b) |
Bitwise OR |
uN_xor(a, b) |
Bitwise XOR |
uN_not(a) |
Bitwise NOT (masked to N bits) |
uN_shl(a, shift) |
Left shift (result masked) |
uN_shr(a, shift) |
Right shift |
uN_eq(a, b) |
Equality comparison |
uN_lt(a, b) |
Less than comparison |
uN_le(a, b) |
Less than or equal comparison |
uN_max() |
Maximum value (2^N - 1) |
uN_min() |
Minimum value (0) |
Signed Integer API (INT_DEFINE):
| Function | Description |
|---|---|
iN_new(value) |
Create N-bit signed integer (sign-extended) |
iN_get(ptr) |
Get sign-extended value |
iN_add(a, b) |
Add two values |
iN_sub(a, b) |
Subtract two values |
iN_mul(a, b) |
Multiply two values |
iN_neg(a) |
Negate value |
iN_eq(a, b) |
Equality comparison |
iN_lt(a, b) |
Less than comparison |
iN_le(a, b) |
Less than or equal comparison |
iN_max() |
Maximum value (2^(N-1) - 1) |
iN_min() |
Minimum value (-2^(N-1)) |
Backing Type Selection:
| Bit Width | Unsigned Backing | Signed Backing |
|---|---|---|
| 1-8 | u8 | i8 |
| 9-16 | u16 | i16 |
| 17-32 | u32 | i32 |
| 33-64 | u64 | i64 |
Fixed-size bit collections for efficient flag management. Define bitsets with BITSET_DEFINE(N) for N bits (1-64).
#include <cyan/bitset.h>
BITSET_DEFINE(8); // Bitset_8: 8-bit bitset
BITSET_DEFINE(16); // Bitset_16: 16-bit bitset
i32 main(void) {
// Create empty bitset
Bitset_8 bs = bitset_8_new();
// Set, clear, toggle bits
bitset_8_set(&bs, 0); // Set bit 0
bitset_8_set(&bs, 3); // Set bit 3
bitset_8_toggle(&bs, 3); // Toggle bit 3 (now clear)
bitset_8_clear(&bs, 0); // Clear bit 0
// Query bits
bool is_set = bitset_8_get(&bs, 0); // false
// Create from raw value
Bitset_8 set1 = bitset_8_from_raw(0b00001111); // Bits 0-3
Bitset_8 set2 = bitset_8_from_raw(0b00111100); // Bits 2-5
// Set operations
Bitset_8 union_set = bitset_8_union(&set1, &set2); // OR
Bitset_8 intersect = bitset_8_intersect(&set1, &set2); // AND
Bitset_8 diff = bitset_8_diff(&set1, &set2); // set1 & ~set2
Bitset_8 comp = bitset_8_complement(&set1); // ~set1 (masked)
// Utility functions
u8 count = bitset_8_count(&bs); // Number of set bits
bool all = bitset_8_all(&bs); // All bits set?
bool any = bitset_8_any(&bs); // Any bit set?
bool none = bitset_8_none(&bs); // No bits set?
bool equal = bitset_8_eq(&set1, &set2); // Equality check
return 0;
}Bitset API:
| Function | Description |
|---|---|
bitset_N_new() |
Create bitset with all bits cleared |
bitset_N_from_raw(value) |
Create bitset from raw integer value |
bitset_N_set(bs, index) |
Set bit at index (panics if out of bounds) |
bitset_N_clear(bs, index) |
Clear bit at index |
bitset_N_get(bs, index) |
Get bit at index (returns bool) |
bitset_N_toggle(bs, index) |
Toggle bit at index |
bitset_N_union(a, b) |
Union of two bitsets (OR) |
bitset_N_intersect(a, b) |
Intersection of two bitsets (AND) |
bitset_N_diff(a, b) |
Difference (a AND NOT b) |
bitset_N_complement(bs) |
Complement (NOT, masked to N bits) |
bitset_N_eq(a, b) |
Check equality |
bitset_N_count(bs) |
Count set bits (popcount) |
bitset_N_all(bs) |
Check if all N bits are set |
bitset_N_any(bs) |
Check if any bit is set |
bitset_N_none(bs) |
Check if no bits are set |
Convenience Macros (vtable-based):
| Macro | Description |
|---|---|
BS_SET(bs, i) |
Set bit at index |
BS_CLEAR(bs, i) |
Clear bit at index |
BS_GET(bs, i) |
Get bit at index |
BS_TOGGLE(bs, i) |
Toggle bit at index |
BS_UNION(a, b) |
Union of two bitsets |
BS_INTERSECT(a, b) |
Intersection of two bitsets |
BS_DIFF(a, b) |
Difference of two bitsets |
BS_COMPLEMENT(bs) |
Complement of bitset |
BS_EQ(a, b) |
Check equality |
BS_COUNT(bs) |
Count set bits |
BS_ALL(bs) |
Check if all bits set |
BS_ANY(bs) |
Check if any bit set |
BS_NONE(bs) |
Check if no bits set |
Define named flags with FLAGS_DEFINE for type-safe flag manipulation:
#include <cyan/bitset.h>
// Define named flags
FLAGS_DEFINE(Permissions, READ, WRITE, EXECUTE, HIDDEN);
// Creates: Permissions_READ = 0, Permissions_WRITE = 1, etc.
// Creates: Permissions_COUNT = 4
BITSET_DEFINE(4); // Bitset for 4 flags
i32 main(void) {
Bitset_4 perms = bitset_4_new();
// Set flags using names
FLAGS_SET(perms, Permissions_READ);
FLAGS_SET(perms, Permissions_WRITE);
// Check flags
if (FLAGS_HAS(perms, Permissions_READ)) {
printf("Has read permission\n");
}
// Clear flags
FLAGS_CLEAR(perms, Permissions_WRITE);
return 0;
}Named Flags API:
| Macro | Description |
|---|---|
FLAGS_DEFINE(Name, ...) |
Define named flags with sequential bit positions |
FLAGS_SET(bs, flag) |
Set the specified flag |
FLAGS_CLEAR(bs, flag) |
Clear the specified flag |
FLAGS_HAS(bs, flag) |
Check if flag is set |
The panic handler is invoked for unrecoverable errors in the Cyan library. When a panic occurs, the default behavior is to print diagnostic information (file, line number, and error message) to stderr and then abort the program.
Default Behavior:
// Default panic output format:
// PANIC at filename.c:42: error messageThe default CYAN_PANIC macro prints the file name, line number, and a descriptive message before calling abort(). This provides clear debugging information when something goes wrong.
Panics are triggered in the following situations:
| Scenario | Description |
|---|---|
unwrap() on None |
Attempting to extract a value from an empty Option |
unwrap_ok() on Err |
Attempting to extract a success value from an error Result |
unwrap_err() on Ok |
Attempting to extract an error value from a success Result |
| Memory allocation failure | When malloc() or realloc() returns NULL in collection operations |
| Resuming finished coroutine | Attempting to resume a coroutine that has already completed |
You can override the default panic behavior by defining CYAN_PANIC before including any Cyan headers:
// Define custom panic handler BEFORE including Cyan headers
#define CYAN_PANIC(msg) do { \
fprintf(stderr, "[FATAL] %s:%d - %s\n", __FILE__, __LINE__, msg); \
/* Add custom logging, cleanup, or crash reporting here */ \
abort(); \
} while(0)
#include <cyan/cyan.h>
// Now all panics will use your custom handlerImportant: The custom handler must be defined before any Cyan header is included, as the panic macro is checked with #ifndef and only defined if not already present.
| Macro | Description |
|---|---|
CYAN_PANIC(msg) |
Trigger a panic with the given message. Prints file, line, and message to stderr, then calls abort(). Can be overridden by user. |
CYAN_PANIC_EXPR(msg, dummy) |
Internal helper for panics in expression contexts. Used where a value must be returned (e.g., ternary operators). The dummy value satisfies type requirements but is never returned. |
Explicit nullable value handling that makes absence explicit in code.
#include <cyan/option.h>
OPTION_DEFINE(i32); // Define Option_i32 type
Option_i32 find_value(i32 arr[], usize len, i32 target) {
for (usize i = 0; i < len; i++) {
if (arr[i] == target) return Some(i32, arr[i]);
}
return None(i32);
}
i32 main(void) {
i32 arr[] = {1, 2, 3, 4, 5};
Option_i32 result = find_value(arr, 5, 3);
if (is_some(result)) {
printf("Found: %d\n", unwrap(result));
}
// Use unwrap_or for a default value
i32 val = unwrap_or(result, -1);
// Transform with map_option
Option_i32 doubled = map_option(result, i32, double_fn);
return 0;
}Option API:
| Function | Description |
|---|---|
Some(T, val) |
Create Option containing a value |
None(T) |
Create empty Option |
is_some(opt) |
Check if Option has value |
is_none(opt) |
Check if Option is empty |
unwrap(opt) |
Extract value (panics if None) |
unwrap_or(opt, default) |
Extract value or return default |
map_option(opt, T_out, fn) |
Transform the contained value |
Convenience Macros (vtable-based):
| Macro | Description |
|---|---|
OPT_IS_SOME(opt) |
Check if Option has value |
OPT_IS_NONE(opt) |
Check if Option is empty |
OPT_UNWRAP(opt) |
Extract value (panics if None) |
OPT_UNWRAP_OR(opt, def) |
Extract value or return default |
Explicit error handling without relying on errno or error codes.
#include <cyan/result.h>
RESULT_DEFINE(i32, const_charp); // Define Result_i32_const_charp
Result_i32_const_charp parse_positive(const char *str) {
i32 val = atoi(str);
if (val <= 0) return Err(i32, const_charp, "must be positive");
return Ok(i32, const_charp, val);
}
i32 main(void) {
Result_i32_const_charp res = parse_positive("42");
if (is_ok(res)) {
printf("Parsed: %d\n", unwrap_ok(res));
} else {
printf("Error: %s\n", unwrap_err(res));
}
// Use unwrap_ok_or for default
i32 val = unwrap_ok_or(res, 0);
// Transform success value
Result_i32_const_charp doubled = map_result(res, i32, const_charp, double_fn);
return 0;
}Result API:
| Function | Description |
|---|---|
Ok(T, E, val) |
Create Result with success value |
Err(T, E, err) |
Create Result with error value |
is_ok(res) |
Check if Result is success |
is_err(res) |
Check if Result is error |
unwrap_ok(res) |
Extract success value (panics if Err) |
unwrap_err(res) |
Extract error value (panics if Ok) |
unwrap_ok_or(res, default) |
Extract success or return default |
map_result(res, T_out, E, fn) |
Transform success value |
map_err(res, T, E_out, fn) |
Transform error value |
Convenience Macros (vtable-based):
| Macro | Description |
|---|---|
RES_IS_OK(res) |
Check if Result is success |
RES_IS_ERR(res) |
Check if Result is error |
RES_UNWRAP_OK(res) |
Extract success value (panics if Err) |
RES_UNWRAP_ERR(res) |
Extract error value (panics if Ok) |
RES_UNWRAP_OK_OR(res, def) |
Extract success or return default |
Generic dynamic arrays with automatic growth and bounds-checked access.
#include <cyan/vector.h>
OPTION_DEFINE(i32); // Required for Option_i32
VECTOR_DEFINE(i32); // Define Vec_i32 type
i32 main(void) {
Vec_i32 v = vec_i32_new();
// Push elements
vec_i32_push(&v, 10);
vec_i32_push(&v, 20);
vec_i32_push(&v, 30);
// Access with bounds checking (returns Option)
Option_i32 elem = vec_i32_get(&v, 1);
if (is_some(elem)) {
printf("Element at 1: %d\n", unwrap(elem));
}
// Out of bounds returns None
Option_i32 invalid = vec_i32_get(&v, 100); // None
// Pop elements (LIFO)
while (is_some(elem = vec_i32_pop(&v))) {
printf("Popped: %d\n", unwrap(elem));
}
// Pre-allocate capacity
Vec_i32 preallocated = vec_i32_with_capacity(100);
vec_i32_free(&v);
vec_i32_free(&preallocated);
return 0;
}Vector API:
| Function | Description |
|---|---|
vec_T_new() |
Create empty vector |
vec_T_with_capacity(cap) |
Create vector with pre-allocated capacity |
vec_T_push(v, elem) |
Append element (auto-grows) |
vec_T_pop(v) |
Remove and return last element as Option |
vec_T_get(v, idx) |
Get element at index as Option |
vec_T_len(v) |
Get current length |
vec_T_free(v) |
Free vector memory |
Convenience Macros (vtable-based):
| Macro | Description |
|---|---|
VEC_PUSH(v, elem) |
Append element |
VEC_POP(v) |
Remove and return last element |
VEC_GET(v, idx) |
Get element at index |
VEC_LEN(v) |
Get current length |
VEC_FREE(v) |
Free vector memory |
Non-owning views into contiguous sequences with bounds information.
#include <cyan/slice.h>
OPTION_DEFINE(i32);
VECTOR_DEFINE(i32);
SLICE_DEFINE(i32);
i32 main(void) {
// Create slice from C array
i32 arr[] = {1, 2, 3, 4, 5};
Slice_i32 s = slice_i32_from_array(arr, 5);
// Bounds-checked access
Option_i32 elem = slice_i32_get(s, 2); // Some(3)
// Create subslice
Slice_i32 sub = slice_i32_subslice(s, 1, 4); // {2, 3, 4}
// Create slice from vector
Vec_i32 v = vec_i32_new();
vec_i32_push(&v, 10);
Slice_i32 vs = slice_i32_from_vec(&v);
printf("Length: %zu\n", slice_i32_len(s));
vec_i32_free(&v);
return 0;
}Slice API:
| Function | Description |
|---|---|
slice_T_from_array(arr, len) |
Create slice from C array |
slice_T_from_vec(v) |
Create slice from vector |
slice_T_get(s, idx) |
Get element at index as Option |
slice_T_subslice(s, start, end) |
Create subslice view |
slice_T_len(s) |
Get slice length |
Convenience Macros (vtable-based):
| Macro | Description |
|---|---|
SLICE_GET(s, idx) |
Get element at index |
SLICE_SUBSLICE(s, start, end) |
Create subslice view |
SLICE_LEN(s) |
Get slice length |
Type-safe hash maps with O(1) average lookups using FNV-1a hashing.
#include <cyan/hashmap.h>
OPTION_DEFINE(i32);
HASHMAP_DEFINE(i32, i32); // HashMap_i32_i32
HASHMAP_ITER_DEFINE(i32, i32); // Iterator support
i32 main(void) {
HashMap_i32_i32 m = hashmap_i32_i32_new();
// Insert key-value pairs
hashmap_i32_i32_insert(&m, 1, 100);
hashmap_i32_i32_insert(&m, 2, 200);
hashmap_i32_i32_insert(&m, 3, 300);
// Lookup (returns Option)
Option_i32 val = hashmap_i32_i32_get(&m, 2);
if (is_some(val)) {
printf("Key 2 -> %d\n", unwrap(val));
}
// Check existence
if (hashmap_i32_i32_contains(&m, 1)) {
printf("Key 1 exists\n");
}
// Remove entry
Option_i32 removed = hashmap_i32_i32_remove(&m, 1);
// Iterate over entries
HashMapIter_i32_i32 it = hashmap_i32_i32_iter(&m);
Option_MapPair_i32_i32 pair;
while ((pair = hashmap_i32_i32_iter_next(&it)).has_value) {
printf("%d -> %d\n", pair.value.key, pair.value.value);
}
printf("Size: %zu\n", hashmap_i32_i32_len(&m));
hashmap_i32_i32_free(&m);
return 0;
}HashMap API:
| Function | Description |
|---|---|
hashmap_K_V_new() |
Create empty map |
hashmap_K_V_with_capacity(cap) |
Create map with initial capacity |
hashmap_K_V_insert(m, key, value) |
Insert or update entry |
hashmap_K_V_get(m, key) |
Get value as Option |
hashmap_K_V_contains(m, key) |
Check if key exists |
hashmap_K_V_remove(m, key) |
Remove entry, return value as Option |
hashmap_K_V_len(m) |
Get number of entries |
hashmap_K_V_iter(m) |
Create iterator |
hashmap_K_V_iter_next(it) |
Get next key-value pair |
hashmap_K_V_free(m) |
Free map memory |
Convenience Macros (vtable-based):
| Macro | Description |
|---|---|
MAP_INSERT(m, k, v) |
Insert or update entry |
MAP_GET(m, k) |
Get value as Option |
MAP_CONTAINS(m, k) |
Check if key exists |
MAP_REMOVE(m, k) |
Remove entry, return value |
MAP_LEN(m) |
Get number of entries |
MAP_FREE(m) |
Free map memory |
Heap-allocated, growable strings with safe operations.
#include <cyan/string.h>
i32 main(void) {
// Create strings
String s = string_from("Hello");
String empty = string_new();
String preallocated = string_with_capacity(100);
// Append content
string_append(&s, " World");
string_push(&s, '!');
// Append another String
String suffix = string_from(" - Cyan");
string_append_str(&s, &suffix);
// Get C string for printing
printf("%s\n", string_cstr(&s)); // "Hello World! - Cyan"
printf("Length: %zu\n", string_len(&s));
// Character access with bounds checking
Option_char ch = string_get(&s, 0); // Some('H')
// Slicing
Slice_char slice = string_slice(&s, 0, 5); // "Hello"
// Formatting
String formatted = string_formatted("Value: %d, Pi: %.2f", 42, 3.14);
// Concatenation
String a = string_from("Hello");
String b = string_from(" World");
String combined = string_concat(&a, &b);
// Auto-cleanup (GCC/Clang)
string_auto(auto_str, string_from("Auto cleanup!"));
// auto_str freed automatically at scope exit
string_free(&s);
string_free(&suffix);
string_free(&formatted);
string_free(&a);
string_free(&b);
string_free(&combined);
return 0;
}String API:
| Function | Description |
|---|---|
string_new() |
Create empty string |
string_from(cstr) |
Create from C string |
string_with_capacity(cap) |
Create with pre-allocated capacity |
string_push(s, char) |
Append single character |
string_append(s, cstr) |
Append C string |
string_append_str(s, other) |
Append another String |
string_clear(s) |
Clear content (keeps capacity) |
string_format(s, fmt, ...) |
Append formatted content |
string_formatted(fmt, ...) |
Create new formatted string |
string_cstr(s) |
Get null-terminated C string |
string_len(s) |
Get length |
string_get(s, idx) |
Get character as Option |
string_slice(s, start, end) |
Create slice view |
string_concat(a, b) |
Concatenate two strings |
string_free(s) |
Free string memory |
string_auto(name, init) |
Declare with auto-cleanup |
Convenience Macros (vtable-based):
| Macro | Description |
|---|---|
STR_PUSH(s, c) |
Append single character |
STR_APPEND(s, cstr) |
Append C string |
STR_CLEAR(s) |
Clear content |
STR_GET(s, idx) |
Get character as Option |
STR_LEN(s) |
Get length |
STR_CSTR(s) |
Get null-terminated C string |
STR_SLICE(s, start, end) |
Create slice view |
STR_FREE(s) |
Free string memory |
Higher-order functions for declarative data transformation.
#include <cyan/functional.h>
// Transformation functions
i32 square(i32 x) { return x * x; }
i32 double_it(i32 x) { return x * 2; }
// Predicate functions
bool is_even(i32 x) { return x % 2 == 0; }
// Accumulator functions
i32 add(i32 a, i32 b) { return a + b; }
// Side-effect function
void print_i32(i32 x) { printf("%d ", x); }
i32 main(void) {
i32 numbers[] = {1, 2, 3, 4, 5};
usize len = 5;
// Map - transform each element
i32 squared[5];
map(numbers, len, squared, square);
// squared = {1, 4, 9, 16, 25}
// Filter - select matching elements
i32 evens[5];
usize evens_len;
filter(numbers, len, evens, &evens_len, is_even);
// evens = {2, 4}, evens_len = 2
// Reduce - combine into single value
i32 sum;
reduce(sum, numbers, len, 0, add);
// sum = 15
// Foreach - execute side effect
foreach(numbers, len, print_i32);
// prints: 1 2 3 4 5
return 0;
}Vector-specific functional operations:
OPTION_DEFINE(i32);
OPTION_DEFINE(f64);
VECTOR_DEFINE(i32);
VECTOR_DEFINE(f64);
VEC_MAP_DEFINE(i32, f64); // vec_map_i32_to_f64
VEC_FILTER_DEFINE(i32); // vec_filter_i32
VEC_REDUCE_DEFINE(i32, i32); // vec_reduce_i32_to_i32
VEC_FOREACH_DEFINE(i32); // vec_foreach_i32
f64 to_double(i32 x) { return (f64)x; }
Vec_i32 v = vec_i32_new();
// ... push elements ...
Vec_f64 doubles = vec_map_i32_to_f64(&v, to_double);
Vec_i32 filtered = vec_filter_i32(&v, is_even);
i32 total = vec_reduce_i32_to_i32(&v, 0, add);
vec_foreach_i32(&v, print_i32);Functional API:
| Macro | Description |
|---|---|
map(arr, len, out, fn) |
Transform each element |
filter(arr, len, out, out_len, pred) |
Select elements matching predicate |
reduce(result, arr, len, init, acc_fn) |
Combine elements into single value |
foreach(arr, len, fn) |
Execute function on each element |
VEC_MAP_DEFINE(T_in, T_out) |
Generate vector map function |
VEC_FILTER_DEFINE(T) |
Generate vector filter function |
VEC_REDUCE_DEFINE(T, R) |
Generate vector reduce function |
VEC_FOREACH_DEFINE(T) |
Generate vector foreach function |
Scope-based resource cleanup using GCC/Clang's cleanup attribute.
#include <cyan/defer.h>
i32 main(void) {
// Basic defer - executes when scope exits
FILE *f = fopen("test.txt", "r");
if (!f) return 1;
defer({ fclose(f); });
// Multiple defers execute in LIFO order
defer({ printf("First declared, last executed\n"); });
defer({ printf("Last declared, first executed\n"); });
// defer_free - convenience for freeing memory
char *buf = malloc(1024);
defer_free(buf); // Automatically freed and set to NULL
// defer_capture_int - capture value at declaration time
i32 x = 10;
defer_capture_int(x, { printf("Captured: %d\n", _captured_val); });
x = 20; // Change doesn't affect deferred code
// Prints "Captured: 10" on scope exit
// Works with all exit paths: return, break, continue
for (i32 i = 0; i < 5; i++) {
char *temp = malloc(100);
defer_free(temp);
if (i == 3) break; // temp still freed!
}
return 0; // All defers execute here
}Defer API:
| Macro | Description |
|---|---|
defer({ code }) |
Execute code block on scope exit |
defer_free(ptr) |
Free pointer on scope exit (sets to NULL) |
defer_capture_int(val, { code }) |
Defer with captured integer value |
Automatic memory management with unique and shared ownership semantics.
#include <cyan/smartptr.h>
OPTION_DEFINE(i32);
UNIQUE_PTR_DEFINE(i32);
SHARED_PTR_DEFINE(i32);
// Custom destructor
void cleanup_resource(void *ptr) {
printf("Cleaning up: %d\n", *(i32*)ptr);
}
i32 main(void) {
// === Unique Pointer (exclusive ownership) ===
{
unique_ptr(i32, p, 42); // Auto-cleanup on scope exit
printf("Value: %d\n", unique_i32_deref(&p));
// Get raw pointer (doesn't transfer ownership)
i32 *raw = unique_i32_get(&p);
// Move ownership
UniquePtr_i32 moved = unique_i32_move(&p);
// p is now NULL, moved owns the memory
unique_i32_free(&moved);
} // p would be freed here if not moved
// With custom destructor
unique_ptr_with_dtor(i32, p2, 100, cleanup_resource);
// === Shared Pointer (reference counted) ===
SharedPtr_i32 s1 = shared_i32_new(100);
printf("Count: %zu\n", shared_i32_count(&s1)); // 1
SharedPtr_i32 s2 = shared_i32_clone(&s1);
printf("Count: %zu\n", shared_i32_count(&s1)); // 2
printf("Value: %d\n", shared_i32_deref(&s1));
shared_i32_release(&s1); // Count = 1
shared_i32_release(&s2); // Count = 0, memory freed
// === Weak Pointer (non-owning reference) ===
SharedPtr_i32 owner = shared_i32_new(200);
WeakPtr_i32 weak = weak_i32_from_shared(&owner);
// Check if target still exists (standalone function)
if (!weak_i32_is_expired(&weak)) {
// Upgrade to shared pointer
Option_SharedPtr_i32 upgraded = weak_i32_upgrade(&weak);
if (upgraded.has_value) {
printf("Upgraded: %d\n", shared_i32_deref(&upgraded.value));
shared_i32_release(&upgraded.value);
}
}
// Vtable method calls (equivalent to standalone functions)
if (!weak.vt->wptr_is_expired(&weak)) {
Option_SharedPtr_i32 upgraded = weak.vt->wptr_upgrade(&weak);
if (upgraded.has_value) {
printf("Upgraded via vtable: %d\n", shared_i32_deref(&upgraded.value));
shared_i32_release(&upgraded.value);
}
}
// Convenience macros (concise vtable access)
if (!WPTR_IS_EXPIRED(weak)) {
Option_SharedPtr_i32 upgraded = WPTR_UPGRADE(weak);
if (upgraded.has_value) {
printf("Upgraded via macro: %d\n", shared_i32_deref(&upgraded.value));
shared_i32_release(&upgraded.value);
}
}
shared_i32_release(&owner); // Memory freed
// weak_i32_is_expired(&weak) now returns true
WPTR_RELEASE(weak); // Release weak reference (via convenience macro)
return 0;
}Smart Pointer API:
| Function | Description |
|---|---|
unique_ptr(T, name, value) |
Declare unique pointer with auto-cleanup |
unique_T_new(value) |
Create unique pointer |
unique_T_deref(u) |
Dereference |
unique_T_get(u) |
Get raw pointer |
unique_T_move(u) |
Transfer ownership |
unique_T_free(u) |
Free memory |
shared_ptr(T, name, value) |
Declare shared pointer with auto-cleanup |
shared_T_new(value) |
Create shared pointer |
shared_T_clone(s) |
Clone (increment ref count) |
shared_T_deref(s) |
Dereference |
shared_T_count(s) |
Get reference count |
shared_T_release(s) |
Release (decrement ref count) |
weak_T_from_shared(s) |
Create weak reference |
weak_T_is_expired(w) |
Check if target freed |
weak_T_upgrade(w) |
Upgrade to shared pointer |
weak_T_release(w) |
Release weak reference |
Convenience Macros (vtable-based):
| Macro | Description |
|---|---|
UPTR_GET(u) |
Get raw pointer from UniquePtr |
UPTR_DEREF(u) |
Dereference UniquePtr |
UPTR_MOVE(u) |
Transfer ownership from UniquePtr |
UPTR_FREE(u) |
Free UniquePtr |
SPTR_GET(s) |
Get raw pointer from SharedPtr |
SPTR_DEREF(s) |
Dereference SharedPtr |
SPTR_CLONE(s) |
Clone SharedPtr (increment ref count) |
SPTR_COUNT(s) |
Get reference count |
SPTR_RELEASE(s) |
Release SharedPtr (decrement ref count) |
WPTR_IS_EXPIRED(w) |
Check if WeakPtr target freed |
WPTR_UPGRADE(w) |
Upgrade WeakPtr to SharedPtr |
WPTR_RELEASE(w) |
Release WeakPtr |
Ergonomic handling of Option and Result types.
#include <cyan/match.h>
OPTION_DEFINE(i32);
RESULT_DEFINE(i32, const_charp);
i32 main(void) {
// === Option Matching (statement form) ===
Option_i32 opt = Some(i32, 42);
match_option(opt, i32, val,
{ printf("Got value: %d\n", val); },
{ printf("No value\n"); }
);
// === Option Matching (expression form) ===
i32 doubled = match_option_expr(opt, i32, i32, v, v * 2, 0);
// === Result Matching (statement form) ===
Result_i32_const_charp res = Ok(i32, const_charp, 100);
match_result(res, i32, const_charp, val, e,
{ printf("Success: %d\n", val); },
{ printf("Error: %s\n", e); }
);
// === Result Matching (expression form) ===
i32 value = match_result_expr(res, i32, const_charp, i32, v, e, v * 2, -1);
return 0;
}Pattern Matching API:
| Macro | Description |
|---|---|
match_option(opt, T, var, some_branch, none_branch) |
Match Option (statement) |
match_option_expr(opt, T, T_out, var, some_expr, none_expr) |
Match Option (expression) |
match_result(res, T, E, ok_var, err_var, ok_branch, err_branch) |
Match Result (statement) |
match_result_expr(res, T, E, T_out, ok_var, err_var, ok_expr, err_expr) |
Match Result (expression) |
Stackful cooperative multitasking using POSIX ucontext.
#include <cyan/coro.h>
// Generator coroutine
void fibonacci(Coro *self, void *arg) {
i32 a = 0, b = 1;
for (i32 i = 0; i < 10; i++) {
coro_yield_value(self, a);
i32 next = a + b;
a = b;
b = next;
}
}
// Coroutine with argument
void counter(Coro *self, void *arg) {
i32 max = *(i32*)arg;
for (i32 i = 0; i < max; i++) {
printf("Count: %d\n", i);
coro_yield(self); // Yield without value
}
}
i32 main(void) {
// Create and run generator
Coro *fib = coro_new(fibonacci, NULL, 0); // 0 = default stack size
printf("Fibonacci: ");
while (coro_resume(fib)) {
printf("%d ", coro_get_yield(fib, i32));
}
printf("\n");
// Check status
printf("Status: %s\n", coro_is_finished(fib) ? "finished" : "running");
coro_free(fib);
// Coroutine with argument
i32 max = 5;
Coro *cnt = coro_new(counter, &max, 0);
while (coro_resume(cnt)) {
// Process between yields
}
coro_free(cnt);
return 0;
}Coroutine API:
| Function | Description |
|---|---|
coro_new(fn, arg, stack_size) |
Create new coroutine (0 = default stack) |
coro_resume(c) |
Resume execution (returns true if yielded) |
coro_yield(c) |
Yield without value |
coro_yield_value(c, val) |
Yield with value |
coro_get_yield(c, T) |
Get yielded value |
coro_is_finished(c) |
Check if coroutine completed |
coro_status(c) |
Get current status |
coro_free(c) |
Free coroutine resources |
Coroutine Status:
CORO_CREATED- Created but never resumedCORO_RUNNING- Currently executingCORO_SUSPENDED- Yielded, waiting to resumeCORO_FINISHED- Completed execution
CSP-style communication primitives for message passing.
#include <cyan/channel.h>
CHANNEL_DEFINE(i32); // Define Channel_i32
i32 main(void) {
// Create buffered channel with capacity 10
Channel_i32 *ch = chan_i32_new(10);
// Send values
chan_i32_send(ch, 1);
chan_i32_send(ch, 2);
chan_i32_send(ch, 3);
// Receive values (returns Option)
Option_i32 val = chan_i32_recv(ch);
if (is_some(val)) {
printf("Received: %d\n", unwrap(val));
}
// Non-blocking operations
ChanStatus status = chan_i32_try_send(ch, 42);
if (status == CHAN_OK) {
printf("Sent successfully\n");
} else if (status == CHAN_WOULD_BLOCK) {
printf("Channel full\n");
}
Option_i32 maybe = chan_i32_try_recv(ch);
// Close channel (no more sends allowed)
chan_i32_close(ch);
// Drain remaining values
while (is_some(val = chan_i32_recv(ch))) {
printf("Drained: %d\n", unwrap(val));
}
// Check if closed
if (chan_i32_is_closed(ch)) {
printf("Channel is closed\n");
}
chan_i32_free(ch);
return 0;
}Thread-Safe Channels:
// Enable thread safety before including
#define CYAN_CHANNEL_THREADSAFE
#include <cyan/channel.h>
// Now channels use pthread mutexes and condition variables
// for safe concurrent access from multiple threadsChannel API:
| Function | Description |
|---|---|
chan_T_new(capacity) |
Create channel (0 = unbuffered) |
chan_T_send(ch, value) |
Send value (blocks if full) |
chan_T_recv(ch) |
Receive value as Option (blocks if empty) |
chan_T_try_send(ch, value) |
Non-blocking send |
chan_T_try_recv(ch) |
Non-blocking receive |
chan_T_close(ch) |
Close channel |
chan_T_is_closed(ch) |
Check if closed |
chan_T_free(ch) |
Free channel |
Convenience Macros (vtable-based):
| Macro | Description |
|---|---|
CHAN_SEND(ch, val) |
Send value to channel |
CHAN_RECV(ch) |
Receive value from channel |
CHAN_TRY_SEND(ch, val) |
Non-blocking send |
CHAN_TRY_RECV(ch) |
Non-blocking receive |
CHAN_CLOSE(ch) |
Close channel |
CHAN_IS_CLOSED(ch) |
Check if closed |
CHAN_FREE(ch) |
Free channel |
Channel Status:
CHAN_OK- Operation succeededCHAN_CLOSED- Channel is closedCHAN_WOULD_BLOCK- Operation would block (try_* variants)
All Cyan collection types support a method-style API through vtables (virtual method tables). This provides an object-oriented feel while maintaining C's efficiency.
Each type instance contains a pointer to a shared static vtable. All instances of the same type share the same vtable, so the memory overhead is just one pointer per instance.
┌─────────────────────────────────────────────────────────┐
│ Static Memory │
│ ┌─────────────────┐ │
│ │ _vec_i32_vt │ ◄── Single vtable per type │
│ │ (static const) │ │
│ └────────┬────────┘ │
│ │ │
└───────────┼─────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────┐
│ Heap/Stack Memory │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Vec_i32 │ │ Vec_i32 │ │ Vec_i32 │ │
│ │ v1.vt ───┼───┼──────────┼───┼──────────┼──► shared │
│ └──────────┘ └──────────┘ └──────────┘ │
└───────────────────────────────────────────────────────────┘
#include <cyan/vector.h>
OPTION_DEFINE(i32);
VECTOR_DEFINE(i32);
i32 main(void) {
Vec_i32 v = vec_i32_new();
// 1. Standalone function (traditional)
vec_i32_push(&v, 42);
// 2. Vtable method call (OOP-style)
v.vt->push(&v, 42);
// 3. Convenience macro (concise)
VEC_PUSH(v, 42);
// All three are equivalent!
vec_i32_free(&v);
return 0;
}// Vtable method calls
v.vt->push(&v, elem); // Append element
v.vt->pop(&v); // Remove and return last element
v.vt->get(&v, idx); // Get element at index
v.vt->len(&v); // Get length
v.vt->free(&v); // Free memory
// Convenience macros
VEC_PUSH(v, elem) // v.vt->push(&v, elem)
VEC_POP(v) // v.vt->pop(&v)
VEC_GET(v, idx) // v.vt->get(&v, idx)
VEC_LEN(v) // v.vt->len(&v)
VEC_FREE(v) // v.vt->free(&v)// Vtable method calls
m.vt->insert(&m, key, val); // Insert key-value pair
m.vt->get(&m, key); // Get value by key
m.vt->contains(&m, key); // Check if key exists
m.vt->remove(&m, key); // Remove and return value
m.vt->len(&m); // Get number of entries
m.vt->free(&m); // Free memory
// Convenience macros
MAP_INSERT(m, k, v) // m.vt->insert(&m, k, v)
MAP_GET(m, k) // m.vt->get(&m, k)
MAP_CONTAINS(m, k) // m.vt->contains(&m, k)
MAP_REMOVE(m, k) // m.vt->remove(&m, k)
MAP_LEN(m) // m.vt->len(&m)
MAP_FREE(m) // m.vt->free(&m)// Vtable method calls
s.vt->get(s, idx); // Get element at index
s.vt->subslice(s, start, end); // Create subslice
s.vt->len(s); // Get length
// Convenience macros
SLICE_GET(s, idx) // s.vt->get(s, idx)
SLICE_SUBSLICE(s, st, end) // s.vt->subslice(s, st, end)
SLICE_LEN(s) // s.vt->len(s)// Vtable method calls
s.vt->push(&s, c); // Append character
s.vt->append(&s, cstr); // Append C string
s.vt->clear(&s); // Clear content
s.vt->get(&s, idx); // Get character at index
s.vt->len(&s); // Get length
s.vt->cstr(&s); // Get C string
s.vt->slice(&s, start, end); // Create slice
s.vt->free(&s); // Free memory
// Convenience macros
STR_PUSH(s, c) // s.vt->push(&s, c)
STR_APPEND(s, cstr) // s.vt->append(&s, cstr)
STR_CLEAR(s) // s.vt->clear(&s)
STR_GET(s, idx) // s.vt->get(&s, idx)
STR_LEN(s) // s.vt->len(&s)
STR_CSTR(s) // s.vt->cstr(&s)
STR_SLICE(s, st, end) // s.vt->slice(&s, st, end)
STR_FREE(s) // s.vt->free(&s)// Vtable method calls
opt.vt->is_some(&opt); // Check if has value
opt.vt->is_none(&opt); // Check if empty
opt.vt->unwrap(&opt); // Extract value (panics if None)
opt.vt->unwrap_or(&opt, def); // Extract value or default
// Convenience macros
OPT_IS_SOME(opt) // opt.vt->is_some(&opt)
OPT_IS_NONE(opt) // opt.vt->is_none(&opt)
OPT_UNWRAP(opt) // opt.vt->unwrap(&opt)
OPT_UNWRAP_OR(opt, def) // opt.vt->unwrap_or(&opt, def)// Vtable method calls
res.vt->is_ok(&res); // Check if success
res.vt->is_err(&res); // Check if error
res.vt->unwrap_ok(&res); // Extract success value
res.vt->unwrap_err(&res); // Extract error value
res.vt->unwrap_ok_or(&res, def); // Extract success or default
// Convenience macros
RES_IS_OK(res) // res.vt->is_ok(&res)
RES_IS_ERR(res) // res.vt->is_err(&res)
RES_UNWRAP_OK(res) // res.vt->unwrap_ok(&res)
RES_UNWRAP_ERR(res) // res.vt->unwrap_err(&res)
RES_UNWRAP_OK_OR(res, def) // res.vt->unwrap_ok_or(&res, def)// Vtable method calls
u.vt->get(&u); // Get raw pointer
u.vt->deref(&u); // Dereference
u.vt->move(&u); // Transfer ownership
u.vt->free(&u); // Free memory
// Convenience macros
UPTR_GET(u) // u.vt->get(&u)
UPTR_DEREF(u) // u.vt->deref(&u)
UPTR_MOVE(u) // u.vt->move(&u)
UPTR_FREE(u) // u.vt->free(&u)// Vtable method calls
s.vt->get(&s); // Get raw pointer
s.vt->deref(&s); // Dereference
s.vt->clone(&s); // Clone (increment ref count)
s.vt->count(&s); // Get reference count
s.vt->release(&s); // Release (decrement ref count)
// Convenience macros
SPTR_GET(s) // s.vt->get(&s)
SPTR_DEREF(s) // s.vt->deref(&s)
SPTR_CLONE(s) // s.vt->clone(&s)
SPTR_COUNT(s) // s.vt->count(&s)
SPTR_RELEASE(s) // s.vt->release(&s)// Vtable method calls
w.vt->wptr_is_expired(&w); // Check if target has been freed
w.vt->wptr_upgrade(&w); // Upgrade to SharedPtr (returns Option)
w.vt->wptr_release(&w); // Release weak reference
// Convenience macros
WPTR_IS_EXPIRED(w) // w.vt->wptr_is_expired(&w)
WPTR_UPGRADE(w) // w.vt->wptr_upgrade(&w)
WPTR_RELEASE(w) // w.vt->wptr_release(&w)// Vtable method calls (channel is pointer-based)
ch->vt->send(ch, val); // Send value
ch->vt->recv(ch); // Receive value
ch->vt->try_send(ch, val); // Non-blocking send
ch->vt->try_recv(ch); // Non-blocking receive
ch->vt->close(ch); // Close channel
ch->vt->is_closed(ch); // Check if closed
ch->vt->free(ch); // Free channel
// Convenience macros
CHAN_SEND(ch, val) // ch->vt->send(ch, val)
CHAN_RECV(ch) // ch->vt->recv(ch)
CHAN_TRY_SEND(ch, val) // ch->vt->try_send(ch, val)
CHAN_TRY_RECV(ch) // ch->vt->try_recv(ch)
CHAN_CLOSE(ch) // ch->vt->close(ch)
CHAN_IS_CLOSED(ch) // ch->vt->is_closed(ch)
CHAN_FREE(ch) // ch->vt->free(ch)#include <cyan/cyan.h>
OPTION_DEFINE(i32);
VECTOR_DEFINE(i32);
HASHMAP_DEFINE(i32, i32);
i32 main(void) {
// Vector with convenience macros
Vec_i32 nums = vec_i32_new();
VEC_PUSH(nums, 10);
VEC_PUSH(nums, 20);
VEC_PUSH(nums, 30);
printf("Vector length: %zu\n", VEC_LEN(nums));
Option_i32 elem = VEC_GET(nums, 1);
if (OPT_IS_SOME(elem)) {
printf("Element at 1: %d\n", OPT_UNWRAP(elem));
}
// HashMap with convenience macros
HashMap_i32_i32 scores = hashmap_i32_i32_new();
MAP_INSERT(scores, 1, 100);
MAP_INSERT(scores, 2, 200);
if (MAP_CONTAINS(scores, 1)) {
Option_i32 score = MAP_GET(scores, 1);
printf("Score for 1: %d\n", OPT_UNWRAP_OR(score, 0));
}
VEC_FREE(nums);
MAP_FREE(scores);
return 0;
}Text-based serialization using S-expression-like format.
#include <cyan/serialize.h>
i32 main(void) {
// === Serialization ===
char *int_str = serialize_int(42); // "42"
char *dbl_str = serialize_double(3.14); // "3.14"
char *str_str = serialize_string("hello\nworld"); // "\"hello\\nworld\""
// Generic serialize macro (uses _Generic)
char *s1 = serialize(42); // Uses serialize_int
char *s2 = serialize(3.14); // Uses serialize_double
char *s3 = serialize("hello"); // Uses serialize_string
// === Parsing ===
const char *end;
// Parse integer
Result_int_ParseError int_res = parse_int("42 rest", &end);
if (is_ok(int_res)) {
printf("Parsed: %d\n", unwrap_ok(int_res));
// end points to " rest"
}
// Parse double (handles nan, inf, -inf)
Result_double_ParseError dbl_res = parse_double("3.14", NULL);
// Parse quoted string (handles escape sequences)
Result_ParsedString_ParseError str_res = parse_string("\"hello\\nworld\"", &end);
if (is_ok(str_res)) {
char *parsed = unwrap_ok(str_res);
printf("Parsed: %s\n", parsed); // "hello\nworld"
free(parsed); // Caller must free
}
// === Pretty Printing ===
char *pretty = pretty_print("(1 2 (3 4) 5)", 2);
printf("%s\n", pretty);
// Output:
// (
// 1
// 2
// (
// 3
// 4
// )
// 5
// )
free(int_str);
free(dbl_str);
free(str_str);
free(s1);
free(s2);
free(s3);
free(pretty);
return 0;
}Serialization Grammar:
value := atom | list
atom := number | string | symbol
number := ['-'] digit+ ['.' digit+]
string := '"' char* '"'
symbol := alpha (alpha | digit | '_')*
list := '(' value* ')'
Serialization API:
| Function | Description |
|---|---|
serialize_int(val) |
Serialize integer to string |
serialize_long(val) |
Serialize long to string |
serialize_float(val) |
Serialize float to string |
serialize_double(val) |
Serialize double to string |
serialize_string(str) |
Serialize string with escaping |
serialize(val) |
Generic serialize (auto-selects type) |
parse_int(input, end) |
Parse integer, return Result |
parse_double(input, end) |
Parse double, return Result |
parse_string(input, end) |
Parse quoted string, return Result |
pretty_print(str, indent) |
Format with indentation |
Configure the library by defining macros before including headers:
// Custom panic handler
#define CYAN_PANIC(msg) my_panic_handler(msg)
// Collection settings
#define CYAN_DEFAULT_CAPACITY 8 // Initial capacity for vectors, strings
#define CYAN_GROWTH_FACTOR 2 // Growth multiplier when resizing
// HashMap settings
#define CYAN_HASHMAP_INITIAL_CAPACITY 16
#define CYAN_HASHMAP_LOAD_FACTOR 70 // Resize at 70% full
// Coroutine stack size
#define CYAN_CORO_STACK_SIZE (128 * 1024) // 128KB
// Enable thread-safe channels
#define CYAN_CHANNEL_THREADSAFE
// Suppress warnings for unavailable platform-specific types
#define CYAN_SUPPRESS_TYPE_WARNINGS
#include <cyan/cyan.h>Check for available features at compile time:
#include <cyan/cyan.h>
// Library version
#if CYAN_VERSION_AT_LEAST(0, 1, 0)
// Use features from v0.1.0+
#endif
// Platform-specific types
#if CYAN_HAS_INT128
i128 big = ...;
#endif
#if CYAN_HAS_FLOAT128
f128 precise = ...;
#endif
// Compiler features
#if CYAN_HAS_CLEANUP_ATTR
// defer and smart pointers available
#endif
#if CYAN_HAS_GENERIC
// _Generic-based macros available
#endif
#if CYAN_HAS_STMT_EXPR
// Statement expressions available
#endif- C11 compatible compiler (GCC, Clang, or MSVC with C11 support)
- GCC/Clang recommended for:
deferand auto-cleanup features (uses__attribute__((cleanup)))- Statement expressions in pattern matching
- Nested functions in defer
- POSIX system for coroutines (uses
ucontext.h) - pthreads for thread-safe channels
make testSee the examples/ directory for complete example programs:
| Example | Description |
|---|---|
00_primitive_types.c |
Primitive type aliases (i32, u64, f32, etc.) |
01_option_basics.c |
Option type for nullable values |
02_result_error_handling.c |
Result type for error handling |
03_vector_collections.c |
Dynamic arrays with bounds checking |
04_defer_cleanup.c |
Automatic resource cleanup |
05_smart_pointers.c |
Unique and shared pointers |
06_pattern_matching.c |
Pattern matching on Option/Result |
07_functional.c |
Functional primitives (map, filter, reduce) |
08_vtable_api.c |
Vtable method-style API and convenience macros |
09_panic_handler.c |
Panic handler behavior and customization |
16_bitset_integers.c |
Bitsets and custom bit-width integers |
Build and run examples:
cd examples
make
make run # Run all examplesMIT License - see LICENSE file for details.