API Overview

How the asm-rs API is shaped: one-shot functions, the Assembler builder, AssemblyResult, error handling and the compile-time macros.

This page explains how the API fits together and which entry point to reach for. For exact signatures, field types and every method — always current, because it is generated from the source — see docs.rs/asm-rs.

Three ways in

You wantUseCost
Bytes from a string, nothing elseassembleOne call
Labels, a base address, listings, limitsAssemblerA few lines of setup
A constant known at compile timeasm_bytes!Zero at runtime

One-shot

use asm_rs::{assemble, assemble_at, Arch};

// Position-independent: labels resolve relative to 0.
let code = assemble("
    xor edi, edi
    mov eax, 60
    syscall
", Arch::X86_64)?;

// When the code will live at a known address, say so — absolute
// relocations and PC-relative displacements both depend on it.
let at_addr = assemble_at("nop\nret", Arch::X86_64, 0x40_1000)?;

Builder

Assembler is the full pipeline. emit() may be called repeatedly — each call appends — and finish() consumes the assembler to resolve labels, relax branches and produce the output.

use asm_rs::{Assembler, Arch};

let mut asm = Assembler::new(Arch::X86_64);
asm.base_address(0x40_1000);
asm.enable_listing();
asm.define_constant("EXIT_CODE", 0);

asm.emit("
_start:
    mov edi, EXIT_CODE
    mov eax, 60
    syscall
")?;

let result = asm.finish()?;
println!("{}", result.listing());

Configuration is set before emitting: base_address, syntax, optimize, limits, enable_listing, define_constant, define_external and define_preprocessor_symbol. There are also builder methods for emitting data directly — db, dw, dd, dq, ascii, asciz, align, org, fill, space — for when it is easier to call a method than to format a directive.

reset() clears the assembled state while keeping configuration, so one assembler can be reused across independent jobs.

What comes back

AssemblyResult holds the bytes plus everything the linker learned while producing them:

use asm_rs::{Assembler, Arch};

let mut asm = Assembler::new(Arch::X86_64);
asm.emit("start:\n  jmp start")?;
let result = asm.finish()?;

let code: &[u8] = result.bytes();
let entry: Option<u64> = result.label_address("start");

// Labels are sorted by name, so `label_address` is a binary search.
for (name, addr) in result.labels() {
    println!("{name} = {addr:#x}");
}

// Where each label reference was patched — enough to re-link elsewhere.
for reloc in result.relocations() {
    println!("{} at +{:#x} ({:?})", reloc.label, reloc.offset, reloc.kind);
}

into_bytes() takes ownership when the metadata is not needed.

Errors

Every fallible call returns AsmError, which carries a Span — line, column, byte offset and length — so a diagnostic can point at the source. It implements Display, and std::error::Error when the std feature is on.

The variants distinguish causes worth handling differently: an unknown mnemonic, invalid operands, an out-of-range immediate, an undefined or duplicate label, a branch that is out of range or misaligned for its encoding, a relaxation that did not converge, and an exceeded resource limit. Multiple carries several at once when a single emit() produced more than one.

use asm_rs::{assemble, Arch, AsmError};

match assemble("mov rax, rbx, rcx", Arch::X86_64) {
    Ok(bytes) => println!("{} bytes", bytes.len()),
    Err(AsmError::InvalidOperands { detail, span }) => {
        eprintln!("line {}: {detail}", span.line);
    }
    Err(e) => eprintln!("{e}"),
}

Compile-time macros

asm-rs-macros runs the assembler during compilation and emits a constant, so there is no runtime cost — and no runtime failure mode, because a bad instruction is a compile error.

use asm_rs_macros::{asm_array, asm_bytes};

const SHELLCODE: &[u8] = asm_bytes!(x86_64, "xor eax, eax; inc eax; ret");
const NOP: [u8; 1] = asm_array!(x86_64, "nop");

// An optional base address may precede the source.
const AT_ADDR: &[u8] = asm_bytes!(x86_64, 0x400000, "nop; ret");

The architecture token is one of x86, x86_64, arm, thumb, aarch64, rv32 or rv64. Use asm_array! when the length must be known in the type — it produces [u8; N] rather than a slice.

Feature gates

Each architecture backend is a Cargo feature, so a build only pays for the targets it uses. See Configuration for the full list and the defaults.