Skip to content

C Bindings (bindgen)

The nicc bindgen command generates Nic FFI bindings from C header files, enabling seamless interop with C libraries.

Quick Start

bash
# Generate bindings to stdout
nicc bindgen mylib.h

# Generate to file
nicc bindgen mylib.h -o mylib.nic

# With include paths
nicc bindgen raylib.h -I/opt/raylib/include -o raylib.nic

Usage

bash
nicc bindgen <HEADER> [OPTIONS]

Options

OptionDescription
-o FILEOutput file (stdout if not specified)
-I PATHInclude path for clang (can be repeated)

Examples

bash
# SQLite bindings
nicc bindgen /usr/include/sqlite3.h -o sqlite3.nic

# Multiple include paths
nicc bindgen complex.h -I./include -I/usr/local/include

# System library with custom paths
nicc bindgen raylib.h \
  -I/opt/homebrew/include \
  -o raylib.nic

Type Mappings

Primitive Types

C TypeNic Type
voidunit
charchar
signed char, int8_ti8
unsigned char, uint8_tu8
short, int16_ti16
unsigned short, uint16_tu16
int, int32_ti32
unsigned int, uint32_tu32
long long, int64_ti64
unsigned long long, uint64_tu64
floatf32
doublef64
_Bool, boolbool

Pointer Types

C TypeNic Type
T**T
char*string
const char*string
void*opaque
T****T

Complex Types

C TypeNic Type
struct Foo { ... }struct Foo { ... }
enum Bar { ... }enum Bar { ... }
typedef T Aliastype Alias = T
T arr[N][N]T

Generated Output

Functions

C functions become Nic extern fn declarations:

c
// C header
int calculate(int x, int y);
void process(const char* name);
nic
// Generated Nic
extern fn calculate(x: i32, y: i32) -> i32;
extern fn process(name: string) -> unit;

Structs

C structs are converted to Nic structs:

c
// C header
typedef struct {
    int x;
    int y;
} Point;

struct Node {
    int value;
    struct Node* next;
};
nic
// Generated Nic
pub struct Point {
    x: i32,
    y: i32
}

pub struct Node {
    value: i32,
    next: *Node
}

Enums

C enums become Nic enums:

c
// C header
typedef enum {
    RED = 0,
    GREEN = 1,
    BLUE = 2
} Color;
nic
// Generated Nic
pub enum Color {
    RED,
    GREEN,
    BLUE
}

Type Aliases

c
// C header
typedef unsigned int uint;
typedef struct Point Point;
nic
// Generated Nic
type uint = u32;
type Point = Point;

Constants

Numeric #define macros are converted:

c
// C header
#define MAX_SIZE 1024
#define PI 3.14159
nic
// Generated Nic
const MAX_SIZE: i32 = 1024;
const PI: f64 = 3.14159;

Project Integration

Automatic Binding Generation

Add headers to your nic.toml for automatic generation:

toml
[ffi]
headers = ["vendor/sqlite3.h"]
include_paths = ["vendor/"]
libraries = ["sqlite3"]

During build, bindings are generated to .nicc-build/ffi/:

.nicc-build/
└── ffi/
    └── sqlite3.h.nic

Using Generated Bindings

Import the generated bindings in your code:

nic
// Import generated bindings
import "sqlite3.h";  // Imports from .nicc-build/ffi/sqlite3.h.nic

fn main() -> i32 {
    let db: *opaque = nil as *opaque;
    let rc = sqlite3_open(":memory:", &db);
    // ...
    return 0;
}

Manual Workflow

For more control, generate bindings manually:

bash
# Generate bindings
nicc bindgen vendor/mylib.h -o src/bindings/mylib.nic

# Import in your code
# import bindings.mylib;

Common Patterns

Opaque Pointers

C libraries often use opaque handles:

c
// C: opaque handle
typedef struct sqlite3 sqlite3;
int sqlite3_open(const char*, sqlite3**);
nic
// Generated: opaque pointers
extern fn sqlite3_open(filename: string, ppDb: **opaque) -> i32;

// Usage
let db: *opaque = nil as *opaque;
sqlite3_open("test.db", &db);

Callbacks

Function pointer parameters:

c
// C callback
typedef void (*Callback)(int status, void* data);
void register_callback(Callback cb, void* data);
nic
// Generated
extern fn register_callback(cb: *fn(bare, i32, opaque)->unit, data: opaque) -> unit;

// Usage
fn my_callback(status: i32, data: opaque) -> unit {
    printf("Status: %d\n", status);
}

register_callback(my_callback, nil as opaque);

Variadic Functions

Variadic functions are partially supported:

c
// C
int printf(const char* format, ...);
nic
// Generated - only fixed args, variadic part omitted
extern fn printf(format: string) -> i32;

// For full variadic support, use the function directly
extern fn printf(format: string, arg1: i32) -> i32;  // Manual variant

Limitations

  1. Macros: Only simple numeric #define constants are converted
  2. Inline functions: Not supported (compile as C and link)
  3. Bitfields: Not fully supported
  4. Complex macros: Function-like macros require manual wrapping
  5. Variadic functions: Only fixed arguments are bound

Workaround for Complex Cases

Create a C wrapper file for unsupported patterns:

c
// wrapper.c
#include "complex_lib.h"

// Wrap a macro as a function
int get_flag_value(void) {
    return COMPLEX_MACRO_FLAG;
}

// Wrap inline function
int wrapped_inline(int x) {
    return inline_function(x);
}
toml
# nic.toml
[ffi]
sources = ["wrapper.c"]
headers = ["wrapper.h"]  # Your simple wrapper header

Troubleshooting

Missing Headers

Error: fatal error: 'foo.h' file not found

Add the include path:

bash
nicc bindgen mylib.h -I/path/to/headers

Type Not Found

If a type isn't recognized, it's mapped to opaque:

nic
// Unknown type becomes opaque
extern fn foo(handle: opaque) -> unit;

Build Errors

If generated bindings don't compile, check for:

  • Missing struct definitions (forward declarations)
  • Circular type dependencies
  • Platform-specific types

See Also

Released under the MIT License.