machdocs
Home GitHub

External functions

mach's foreign function interface (FFI) is a single declaration form: ext fun. It declares a body-less function with the C ABI - a forward reference the linker resolves at link time. Declare the foreign symbol, call it like any other function, and supply its definition (a C object, archive, or shared library) when you build. This is the only body-less function form mach allows.

Grammar

An ext fun is a function signature with no body. The declaration ends with a semicolon instead of a block.

ext fun NAME(args) RET;
pub ext fun libc_write(fd: i64, buf: *u8, n: i64) i64;

ext fun strlen(s: *u8) i64;             # private, file-local

The C ABI boundary

The C ABI is the contract at the boundary: every argument and return type must be representable in C, and values are passed exactly as the target's C calling convention dictates. mach's fixed-width scalars and pointers correspond directly to their C counterparts.

The exact calling convention is the one declared for the target (for example sysv64 on Linux or win64 on Windows). See Manifest for how a target's abi is set.

Renaming the linker symbol

By default the linker looks up the declared name. The #[symbol("real_name")] decorator overrides it, binding the declaration to a different external symbol.

#[symbol("write")]
pub ext fun libc_write(fd: i64, buf: *u8, n: i64) i64;

Two common reasons to rename:

Library attribution

On a format that attributes each import to a specific dependency - the PE (Windows) import directory - the #[library("...")] decorator pins an ext import to the DLL that exports it.

#[library("ws2_32.dll")]
ext fun WSAStartup(ver: u16, data: *u8) i32;

library composes with symbol: the rename sets the imported symbol's name, library sets the DLL it is imported from.

# imported as `socket` from ws2_32.dll, called as `ws2_socket` in mach.
#[library("ws2_32.dll")] #[symbol("socket")]
ext fun ws2_socket(af: i32, kind: i32, proto: i32) i64;

Linking the definition

An ext fun is only a forward reference. Its definition is supplied at link time, either statically by a precompiled object or archive or dynamically by a shared library bound at load time. Provide those inputs to mach build on the command line or through the manifest. An undefined ext symbol that no input resolves is a link error, so a typo never silently drops a dependency.

mach build . path/to/libfoo.a        # static archive (every member is pulled)
mach build . -L build/libs -l foo    # search dir + name
mach build . -l c                    # link libc dynamically

The same inputs live in the manifest's libs overlay, merged with the command-line inputs:

[target.linux]
libs = ["build/libs/libfoo.a", "c"]

A loose .o object or static .a archive is a static input merged into the binary; a shared .so is a dynamic dependency whose undefined ext symbols bind at load time through an emitted PLT. A static definition always wins over a same-named dynamic import. Dynamic linking is implemented for the ELF (Linux) and PE (Windows) targets; the Mach-O (Darwin) import path is not yet implemented.

Note

The link-input resolution rules - explicit paths, -l/-L search order, and static-vs-dynamic selection - are documented once on the CLI page, and the manifest overlay on the Manifest page.

A worked example

This program imports two libc functions, calls them, and prints a line to standard output. strlen measures the message and libc_write (the renamed write) writes it to file descriptor 1.

use std.runtime;

#[symbol("write")]
pub ext fun libc_write(fd: i64, buf: *u8, n: i64) i64;

ext fun strlen(s: *u8) i64;

#[symbol("main")]
fun main(argc: i64, argv: **u8) i64 {
    val msg: *u8 = "hello from ffi\n";
    val n: i64 = strlen(msg);
    libc_write(1, msg, n);
    ret 0;
}

libc supplies both symbols, so link it dynamically. The result is a dynamically-linked binary that binds write and strlen at load time.

mach build . -l c     # resolves the ext imports against libc.so

See also

  • Visibility - pub and ext modifiers
  • Decorators - the symbol and library directives
  • CLI - external link inputs and resolution
  • Manifest - the libs link overlay
  • Functions - regular function declarations