# `MobDev.StaticNifs`
[🔗](https://github.com/genericjam/mob_dev/blob/main/lib/mob_dev/static_nifs.ex#L1)

Schema, defaults, and C-source generation for the static NIF table.

The static NIF table lives in two C files inside an app's project:

    priv/generated/driver_tab_ios.c
    priv/generated/driver_tab_android.c

Both are linked **before** `libbeam.a` so they override BEAM's empty
built-in `erts_static_nif_tab[]`. With these in place, `load_nif/2`
resolves to the in-binary init function instead of falling back to
`dlopen`, which fails on iOS (App Store rejects bundled `.dylibs`) and
on Android (RTLD_LOCAL hides the parent's `enif_*` symbols from
child libraries).

## Declaring NIFs

An app's `mob.exs` may add to or override the defaults via the
`:static_nifs` key:

    config :mob_dev,
      static_nifs: [
        %{module: :my_native, archs: [:all]}
      ]

Each entry is a map with these fields:

| Field      | Type     | Default   | Meaning                                |
|------------|----------|-----------|----------------------------------------|
| `:module`  | atom     | required  | Erlang module name                     |
| `:init`    | string   | derived   | Init fn name. Defaults to `<mod>_nif_init` |
| `:builtin` | boolean  | `false`   | True for OTP-shipped libs              |
| `:archs`   | [atom]   | `[:all]`  | Where this NIF should appear           |
| `:guard`   | string   | none      | Preprocessor macro that gates the entry |

Valid `:archs` values: `:all`, `:ios`, `:android`, `:ios_sim`,
`:ios_device`, `:android_arm64`, `:android_arm32`.

When `:archs` is a strict subset of a target platform's archs (e.g.
`[:ios_device]` for iOS), set `:guard` to a preprocessor macro that the
build defines only on those archs. The generated C file wraps both the
forward declaration and the table row in `#ifdef <guard>`.

## Defaults

See `default_nifs/0` for the baked-in NIF set. It mirrors the hand-edited
`driver_tab_ios.c` / `driver_tab_android.c` files mob shipped through
v0.5.18 — `regen/1` against an empty user list produces byte-equivalent
output to those files.

# `arch`

```elixir
@type arch() ::
  :all
  | :ios
  | :android
  | :ios_sim
  | :ios_device
  | :android_arm64
  | :android_arm32
```

# `nif_entry`

```elixir
@type nif_entry() :: %{
  :module =&gt; atom(),
  optional(:init) =&gt; String.t(),
  optional(:builtin) =&gt; boolean(),
  optional(:archs) =&gt; [arch()],
  optional(:guard) =&gt; String.t()
}
```

# `platform`

```elixir
@type platform() :: :ios | :android | arch()
```

# `default_nifs`

```elixir
@spec default_nifs() :: [nif_entry()]
```

Returns the baked-in NIF set used by every Mob app.

These match the hand-edited `driver_tab_*.c` files in mob ≤ 0.5.18.
Users append to this list via `:static_nifs` in `mob.exs`.

# `generate`

```elixir
@spec generate(platform(), [nif_entry()], keyword()) :: iodata()
```

Generates the driver_tab source for one platform.

Format is `:c` by default (produces `driver_tab_<platform>.c` matching
the hand-edited reference files byte-for-byte). Pass `format: :zig`
for the Phase 6a Zig output — same semantics, structured as
comptime-friendly Zig (`extern struct` ABI types, `export` for the
C-callable symbols, `if (sqlite_static) ... else ...` in place of
`#ifdef`).

Pure function — given the same nif list it always produces the same
bytes.

# `guarded?`

```elixir
@spec guarded?(nif_entry()) :: boolean()
```

True when the entry carries a `:guard` key. The guard is the user's
explicit opt-in (e.g. `MOB_STATIC_EMLX_NIF`) and gets emitted as a
preprocessor `#ifdef` in C output or a comptime const in Zig output,
independent of whether the entry's archs narrow the platform.

# `init_fn`

```elixir
@spec init_fn(nif_entry()) :: String.t()
```

Returns the init function name for an entry — either the explicit `:init`
value or the conventional `<module>_nif_init`.

# `needs_guard?`

```elixir
@spec needs_guard?(nif_entry(), platform()) :: boolean()
```

Returns true if the entry's archs are a *strict* subset of the platform's
archs (i.e. it's present on this platform but not all of its arches). When
true, the generated entry must be wrapped in `#ifdef <guard>`.

# `on_platform?`

```elixir
@spec on_platform?(nif_entry(), platform()) :: boolean()
```

Returns true if the entry should appear in the generated file for this
platform (i.e. its archs intersect the platform's archs).

# `resolve`

```elixir
@spec resolve(user_nifs :: [nif_entry()]) :: [nif_entry()]
```

Combines the defaults with a user list (typically from
`Application.get_env(:mob_dev, :static_nifs, [])`).

Later entries with the same `:module` override earlier ones. This lets
users replace a default entry — e.g. drop `:sqlite3_nif` by setting
`archs: []` — without forking the default list.

# `validate_entry`

```elixir
@spec validate_entry(nif_entry()) :: :ok | {:error, String.t()}
```

Validates a single entry, returning `:ok` or `{:error, reason}`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
