guides/type-conversion.md

# Type Conversion

This guide covers all supported type conversions between Elixir and C3.

## Conversion Overview

| Elixir Type | C3 Type | Make Function | Get Method |
|-------------|---------|---------------|------------|
| `integer()` | `int` | `make_int/2` | `Term.get_int/1` |
| `integer()` | `uint` | `make_uint/2` | `Term.get_uint/1` |
| `integer()` | `long` | `make_long/2` | `Term.get_long/1` |
| `integer()` | `ulong` | `make_ulong/2` | `Term.get_ulong/1` |
| `float()` | `double` | `make_double/2` | `Term.get_double/1` |
| `atom()` | `char*` | `make_atom/2` | `Term.get_atom_length/1` |
| `binary()` | `ErlNifBinary` | `make_new_binary/3` | `Term.inspect_binary/1` |
| `list()` | `ErlNifTerm[]` | `make_list_from_array/2` | `Term.get_list_cell/3` |
| `tuple()` | `ErlNifTerm[]` | `make_tuple_from_array/2` | `Term.get_tuple/2` |
| `map()` | - | `make_new_map/1` | `Term.map_get/2` |
| `reference()` | - | `make_ref/1` | `Term.is_ref/1` |
| `pid()` | `ErlNifPid` | - | `Term.get_local_pid/1` |

## Integers

### Creating Integers

```c3
import c3nif::term;

// Signed integers
Term result = term::make_int(&e, 42);
Term result = term::make_long(&e, 9223372036854775807);

// Unsigned integers
Term result = term::make_uint(&e, 4294967295);
Term result = term::make_ulong(&e, 18446744073709551615);
```

### Extracting Integers

All extraction methods return optionals (`?` types) that fail with `BADARG` if the term is not the expected type:

```c3
Term arg = term::wrap(argv[0]);

// Extract signed int
int? value = arg.get_int(&e);
if (catch err = value) {
    return term::make_badarg(&e).raw();
}
// Use value safely here

// Extract long (for larger values)
long? big_value = arg.get_long(&e);
```

### Integer Ranges

| Type | Min | Max |
|------|-----|-----|
| `int` | -2,147,483,648 | 2,147,483,647 |
| `uint` | 0 | 4,294,967,295 |
| `long` | -9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 |
| `ulong` | 0 | 18,446,744,073,709,551,615 |

Values outside these ranges will fail extraction with `BADARG`.

## Floats

### Creating Floats

```c3
Term result = term::make_double(&e, 3.14159);
Term result = term::make_double(&e, -273.15);
```

### Extracting Floats

```c3
double? value = arg.get_double(&e);
if (catch err = value) {
    return term::make_badarg(&e).raw();
}
```

Note: Elixir integers are NOT automatically converted to floats. `1` and `1.0` are different types.

## Atoms

### Creating Atoms

```c3
// From null-terminated string
Term ok = term::make_atom(&e, "ok");
Term error = term::make_atom(&e, "error");
Term custom = term::make_atom(&e, "my_custom_atom");

// From string with length
Term atom = term::make_atom_len(&e, "hello", 5);
```

**Warning**: `make_atom` creates atoms unconditionally. Atoms are not garbage collected, so creating atoms from user input can exhaust the atom table. Use `make_existing_atom` for user input:

```c3
// Safe for user input - only succeeds if atom exists
Term? atom = term::make_existing_atom(&e, user_string);
if (catch err = atom) {
    return term::make_badarg(&e).raw();  // Unknown atom
}
```

### Checking Atoms

```c3
if (arg.is_atom(&e)) {
    // It's an atom
}

// Get atom length (to allocate buffer for reading)
uint? len = arg.get_atom_length(&e);
```

## Binaries

Binaries are the most efficient way to pass byte data between Elixir and C3.

### Inspecting Binaries (Zero-Copy)

```c3
import c3nif::binary;

// Get read-only access to binary data
Binary? bin = binary::inspect(&e, arg);
if (catch err = bin) {
    return term::make_badarg(&e).raw();
}

// Access data
char[] data = bin.as_slice();
usz size = bin.size;

// Process the data (read-only)
for (usz i = 0; i < size; i++) {
    char byte = data[i];
    // ...
}
```

### Creating Binaries

```c3
// Create a new binary with allocated space
char* data;
Term result = term::make_new_binary(&e, 100, &data);

// Write data into the buffer
for (usz i = 0; i < 100; i++) {
    data[i] = (char)i;
}

// Return the binary
return result.raw();
```

### Creating from Existing Data

```c3
// From a slice
char[] my_data = "Hello, World!";
Term result = binary::from_slice(&e, my_data);

// Copy existing data
Binary source = /* ... */;
Term copy = binary::copy(&e, &source);
```

### Sub-binaries (Views)

Sub-binaries share memory with the parent:

```c3
// Create a view into an existing binary
Term sub = term::make_sub_binary(&e, parent_term, offset, length);
```

### Binary Size Thresholds

- **Heap binaries** (< 64 bytes): Copied on the process heap
- **Reference-counted binaries** (>= 64 bytes): Shared, reference counted

For large binaries, use `binary::alloc` for reference-counted allocation:

```c3
Binary? bin = binary::alloc(1024 * 1024);  // 1MB
if (catch err = bin) {
    return term::make_badarg(&e).raw();
}
// ... fill data ...
Term result = binary::make_new(&e, &bin);
```

## Lists

### Creating Lists

```c3
// Empty list
Term empty = term::make_empty_list(&e);

// From array
ErlNifTerm[3] items = {
    term::make_int(&e, 1).raw(),
    term::make_int(&e, 2).raw(),
    term::make_int(&e, 3).raw()
};
Term list = term::make_list_from_array(&e, items[0:3]);

// Build list incrementally (prepend - efficient)
Term list = term::make_empty_list(&e);
list = term::make_list_cell(&e, term::make_int(&e, 3), list);
list = term::make_list_cell(&e, term::make_int(&e, 2), list);
list = term::make_list_cell(&e, term::make_int(&e, 1), list);
// Result: [1, 2, 3]
```

### Iterating Lists

```c3
Term head;
Term tail = arg;  // The list term

while (!tail.is_empty_list(&e)) {
    if (tail.get_list_cell(&e, &head, &tail)) |_| {
        // Error - not a proper list
        return term::make_badarg(&e).raw();
    }

    // Process head
    int? value = head.get_int(&e);
    // ...
}
```

### List Length

```c3
uint? len = arg.get_list_length(&e);
if (catch err = len) {
    return term::make_badarg(&e).raw();  // Not a proper list
}
```

## Tuples

### Creating Tuples

```c3
// From array
ErlNifTerm[2] elements = {
    term::make_atom(&e, "ok").raw(),
    term::make_int(&e, 42).raw()
};
Term tuple = term::make_tuple_from_array(&e, elements[0:2]);
// Result: {:ok, 42}
```

### Extracting Tuples

```c3
ErlNifTerm* elements;
int? arity = arg.get_tuple(&e, &elements);
if (catch err = arity) {
    return term::make_badarg(&e).raw();
}

// Access elements
if (arity == 2) {
    Term first = term::wrap(elements[0]);
    Term second = term::wrap(elements[1]);
    // ...
}
```

### Common Tuple Patterns

```c3
// Create {:ok, value}
Term result = term::make_ok_tuple(&e, value);

// Create {:error, reason}
Term result = term::make_error_tuple(&e, term::make_atom(&e, "invalid"));

// Shorthand for error atoms
Term result = term::make_error_atom(&e, "not_found");
```

## Maps

### Creating Maps

```c3
// Empty map
Term map = term::make_new_map(&e);

// Add entries
Term? map2 = map.map_put(&e, term::make_atom(&e, "name"), term::make_atom(&e, "alice"));
if (catch err = map2) {
    return term::make_badarg(&e).raw();
}

Term? map3 = map2.map_put(&e, term::make_atom(&e, "age"), term::make_int(&e, 30));
```

### Reading Maps

```c3
// Get value by key
Term key = term::make_atom(&e, "name");
Term? value = arg.map_get(&e, key);
if (catch err = value) {
    // Key not found
}

// Get map size
usz? size = arg.get_map_size(&e);
```

## PIDs

### Extracting PIDs

```c3
ErlNifPid? pid = arg.get_local_pid(&e);
if (catch err = pid) {
    return term::make_badarg(&e).raw();
}

// Use PID for sending messages, monitoring, etc.
```

### Checking PIDs

```c3
if (arg.is_pid(&e)) {
    // It's a PID
}
```

## References

### Creating References

```c3
Term ref = term::make_ref(&e);  // Unique reference
```

### Checking References

```c3
if (arg.is_ref(&e)) {
    // It's a reference
}
```

## Type Checking

Before extraction, you can check term types:

```c3
if (arg.is_atom(&e)) { /* ... */ }
if (arg.is_binary(&e)) { /* ... */ }
if (arg.is_list(&e)) { /* ... */ }
if (arg.is_tuple(&e)) { /* ... */ }
if (arg.is_map(&e)) { /* ... */ }
if (arg.is_number(&e)) { /* ... */ }
if (arg.is_pid(&e)) { /* ... */ }
if (arg.is_ref(&e)) { /* ... */ }
if (arg.is_fun(&e)) { /* ... */ }
if (arg.is_port(&e)) { /* ... */ }
if (arg.is_empty_list(&e)) { /* ... */ }
```

## Term Comparison

```c3
// Identity check
if (term1 == term2) {
    // Same term (identical)
}

// Ordering comparison
int cmp = term1.compare_to(term2);
if (cmp < 0) { /* term1 < term2 */ }
if (cmp == 0) { /* term1 == term2 */ }
if (cmp > 0) { /* term1 > term2 */ }
```

## Best Practices

1. **Always handle extraction failures** - Use the `?` optional pattern and `catch`

2. **Use appropriate integer sizes** - `int` for small values, `long` for large ones

3. **Avoid creating atoms from user input** - Use `make_existing_atom` instead

4. **Use binaries for byte data** - More efficient than charlists

5. **Build lists by prepending** - `make_list_cell` is O(1), appending is O(n)

6. **Check types before extraction** - Use `is_*` methods for defensive coding