NIFs cuando es necesario

Código nativo para operaciones críticas de rendimiento.⏱️ 2.5 horas

¿Qué son los NIFs?

NIFs (Native Implemented Functions) son funciones escritas en C/C++/Rust que se ejecutan directamente en el contexto del scheduler de la BEAM.

⚠️ Peligros de NIFs

Un NIF que bloquea o crashea afecta directamente al scheduler. Puede congelar o crashear toda la VM. Usa NIFs solo cuando realmente los necesites y después de agotar opciones puras de Elixir.

Rustler: NIFs en Rust

# mix.exs
{:rustler, "~> 0.29"}

# Generar scaffold
mix rustler.new

Ejemplo: hash rápido

// native/fast_hash/src/lib.rs
use rustler::{Binary, NifResult};
use xxhash_rust::xxh3::xxh3_64;

#[rustler::nif]
fn hash(data: Binary) -> u64 {
    xxh3_64(data.as_slice())
}

rustler::init!("Elixir.FastHash", [hash]);
# lib/fast_hash.ex
defmodule FastHash do
  use Rustler, otp_app: :my_app, crate: "fast_hash"

  def hash(_data), do: :erlang.nif_error(:nif_not_loaded)
end

Dirty NIFs para operaciones largas

// Dirty CPU scheduler (no bloquea scheduler normal)
#[rustler::nif(schedule = "DirtyCpu")]
fn expensive_calculation(data: Binary) -> Vec<u8> {
    // Operación que toma > 1ms
}

// Dirty I/O scheduler
#[rustler::nif(schedule = "DirtyIo")]
fn blocking_io() -> NifResult<String> {
    // I/O que bloquea
}

Yielding NIFs

// Para operaciones muy largas, yield periódicamente
#[rustler::nif]
fn process_large_data(env: Env, data: Binary) -> NifResult<Term> {
    let mut work_done = 0;

    for chunk in data.as_slice().chunks(1000) {
        // Procesar chunk
        work_done += process_chunk(chunk);

        // Yield al scheduler periódicamente
        if work_done % 10000 == 0 {
            if rustler::schedule::consume_timeslice(env, 1) {
                // Reschedule si se acabó el timeslice
                return Err(rustler::Error::RaiseAtom("reschedule"));
            }
        }
    }

    Ok(work_done.encode(env))
}

Cuándo usar NIFs

Caso de usoNIF?Alternativa
Crypto/hashingSí, si no hay NIF de OTP:crypto (ya es NIF)
Parsing binario complejoQuizásBinary pattern matching
Cálculos numéricos intensivosNx/EXLA
JSON encode/decodeNoJason/jiffy
CompresiónYa existe:zlib

Ports como alternativa segura

# Port: proceso externo, no crashea la VM
port = Port.open({:spawn, "./my_program"}, [:binary])
send(port, {self(), {:command, data}})

receive do
  {^port, {:data, result}} -> result
end
Ejercicio 20.1 NIF de checksum Intermedio

Implementa un NIF en Rust para calcular checksums CRC32:

  • Usa la crate crc32fast
  • Benchmark contra :erlang.crc32
  • Implementa versión dirty para datos grandes
Ejercicio 20.2 Evaluar necesidad de NIF Avanzado

Toma una función de tu protocolo binario:

  • Identifica si es candidata a NIF
  • Implementa versión NIF y compara
  • Documenta trade-offs (complejidad vs rendimiento)

Conexión con el proyecto final

Probablemente NO necesitaremos NIFs para el sistema de distribución. Elixir puro suele ser suficiente. Sin embargo, si identificamos cuellos de botella en: