README.md

# C3nif

**Ergonomic Erlang/Elixir NIFs using C3**

C3nif is a library for writing Erlang/Elixir Native Implemented Functions (NIFs) using the [C3 programming language](https://c3-lang.org). If you know C but find raw NIF code tedious, C3nif gives you a cleaner API with less boilerplate.

## Why C3nif?

- **Performance**: Compiles to native code, same as C
- **Less boilerplate**: Wrapper types and helpers cut down on repetitive NIF code
- **Explicit error handling**: C3's optional types (`int?`) make error paths visible
- **Familiar syntax**: If you know C, you can read C3
- **Not Rust**: No borrow checker to fight with (but also no memory safety guarantees)

## Quick Example

```elixir
defmodule MyApp.Math do
  use C3nif, otp_app: :my_app

  ~n"""
  module math_nif;

  import c3nif;
  import c3nif::erl_nif;
  import c3nif::env;
  import c3nif::term;

  fn ErlNifTerm add_one(
      ErlNifEnv* raw_env,
      CInt argc,
      ErlNifTerm* argv
  ) {
      Env e = env::wrap(raw_env);
      Term arg0 = term::wrap(argv[0]);

      int? value = arg0.get_int(&e);
      if (catch err = value) {
          return term::make_badarg(&e).raw();
      }

      return term::make_int(&e, value + 1).raw();
  }
  """

  # Elixir function stub (will be replaced by NIF)
  def add_one(_n), do: :erlang.nif_error(:nif_not_loaded)
end
```

## Installation

Add `c3nif` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:c3nif, "~> 0.1.0"}
  ]
end
```

Ensure you have the C3 compiler installed:

```bash
# Install C3 compiler (version 0.7.7 or later required)
# See https://c3-lang.org/getting-started/prebuilt-binaries/
```

## Core Features

### Type Conversions

C3nif wraps Erlang terms in a `Term` type with methods that return optionals on failure:

```c3
fn ErlNifTerm double_it(
    ErlNifEnv* raw_env,
    CInt argc,
    ErlNifTerm* argv
) {
    Env e = env::wrap(raw_env);
    Term arg = term::wrap(argv[0]);

    // Returns optional - you handle the error or it won't compile
    int? value = arg.get_int(&e);
    if (catch err = value) {
        return term::make_badarg(&e).raw();
    }

    return term::make_int(&e, value * 2).raw();
}
```

### Environment Management

Process-bound and process-independent environments:

```c3
// Process-bound (standard NIF call)
Env e = env::wrap(raw_env);

// Process-independent (for async operations)
env::OwnedEnv? owned = env::new_owned_env();
if (catch err = owned) {
    // Handle allocation failure
}
Env async_env = owned.as_env();
// ... build terms, send messages ...
owned.free();
```

### Term Operations

Type checking, creation, and extraction:

```c3
// Type checking
if (arg.is_atom(&e)) { ... }
if (arg.is_list(&e)) { ... }

// Integer operations
Term result = term::make_int(&e, 42);
int? extracted = arg.get_int(&e);

// List operations
Term empty = term::make_empty_list(&e);
Term list = term::make_list_cell(&e, head, tail);

// Map operations
Term map = term::make_new_map(&e);
Term? updated = map.map_put(&e, key, value);

// Comparison (with operator overloading)
if (term1 == term2) { ... }
```

### Resource Management

Resources let you wrap native data structures and pass them to Erlang as opaque references:

```c3
import c3nif::resource;

// Define your native struct
struct Counter {
    int value;
}

// Destructor called when resource is garbage collected
fn void counter_dtor(ErlNifEnv* env, void* obj) {
    // Cleanup code here (Counter memory is freed automatically)
}

// Register in on_load callback
fn CInt on_load(ErlNifEnv* env_raw, void** priv, ErlNifTerm load_info) {
    Env e = env::wrap(env_raw);
    resource::register_type(&e, "Counter", &counter_dtor)!!;
    return 0;
}

// Create a resource
fn ErlNifTerm create_counter(ErlNifEnv* env_raw, CInt argc, ErlNifTerm* argv) {
    Env e = env::wrap(env_raw);

    void* ptr = resource::alloc("Counter", Counter.sizeof)!!;
    Counter* c = (Counter*)ptr;
    c.value = 42;

    Term t = resource::make_term(&e, ptr);
    resource::release(ptr);  // Term now owns the reference
    return t.raw();
}

// Use a resource
fn ErlNifTerm get_counter(ErlNifEnv* env_raw, CInt argc, ErlNifTerm* argv) {
    Env e = env::wrap(env_raw);
    Term arg = term::wrap(argv[0]);

    void* ptr = resource::get("Counter", &e, arg)!!;
    Counter* c = (Counter*)ptr;

    return term::make_int(&e, c.value).raw();
}
```

### Memory Allocation

BEAM-tracked memory allocation for use with C3 standard library collections:

```c3
import c3nif::allocator;

// Simple allocation
void* ptr = allocator::alloc(1024);
if (!ptr) {
    return term::make_error_atom(&e, "alloc_failed").raw();
}
defer allocator::free(ptr);

// Zero-initialized allocation
void* zeroed = allocator::calloc(256);

// Reallocation (preserves data)
void* grown = allocator::realloc(ptr, 2048);

// With C3 Allocator interface (for collections)
allocator::BeamAllocator beam;
List{int} numbers;
numbers.init(&beam);
defer numbers.free();
```

**Thread Safety**: All allocator functions are thread-safe and can be called from any thread (scheduler, dirty scheduler, or user-created).

**VM Integration**: All allocations are tracked by the BEAM VM and visible in `erlang:memory()` reports.

**Strict Pairing**: Memory allocated with `allocator::alloc` must be freed with `allocator::free`. Never mix with system `malloc`/`free`, binary allocators, or resource allocators.

## Supported Types

| Erlang/Elixir | C3 Type | Operations |
|---------------|---------|------------|
| `integer()` | `int`, `uint`, `long`, `ulong` | `make_int`, `get_int`, etc. |
| `float()` | `double` | `make_double`, `get_double` |
| `atom()` | `char*` | `make_atom`, `make_existing_atom` |
| `binary()` | `ErlNifBinary` | `make_new_binary`, `inspect_binary` |
| `list()` | `ErlNifTerm[]` | `make_list_from_array`, `get_list_cell` |
| `tuple()` | `ErlNifTerm[]` | `make_tuple_from_array`, `get_tuple` |
| `map()` | - | `make_new_map`, `map_put`, `map_get` |
| `reference()` | - | `make_ref`, `is_ref` |
| `pid()` | `ErlNifPid` | `get_local_pid` |
| `resource()` | `void*` | `resource::alloc`, `resource::get`, `resource::make_term` |

## Development

```bash
# Clone the repository
git clone https://github.com/tristanperalta/c3nif.git
cd c3nif

# Install dependencies
mix deps.get

# Run tests (automatically compiles C3 library)
mix test
```

## Requirements

- Elixir 1.18+
- C3 compiler 0.7.7+
- Linux x86_64

## You Can Still Crash the VM

C3nif makes NIFs more ergonomic, but it doesn't make them memory-safe. You're still writing native code. Things that will crash your VM:

- Segfaults from null pointers or use-after-free
- Buffer overflows
- Storing terms beyond their environment's lifetime
- Using `!!` to unwrap errors instead of handling them

The `!!` operator is convenient but dangerous - it panics on error:

```c3
// This will crash if allocation fails:
void* ptr = resource::alloc("Counter", Counter.sizeof)!!;

// Prefer explicit handling:
void*? ptr = resource::alloc("Counter", Counter.sizeof);
if (catch err = ptr) {
    return term::make_error_atom(&e, "alloc_failed").raw();
}
```

Other things to remember:
- Keep NIFs under 1ms (or use dirty schedulers)
- Test edge cases - bad input shouldn't crash the VM
- Run with AddressSanitizer during development

## License

MIT License

---

**Note**: This project is in active development. The API may change before version 1.0.0.