oven-sh/bun · error · Error

slice/str param `${ty}` is not FFI-safe; pass (ptr, len)

Error message

slice/str param `${ty}` is not FFI-safe; pass (ptr, len)

What it means

generate-host-exports.ts generates C-ABI thunks for Rust functions marked with a `// HOST_EXPORT(...)` comment. Slices (&[T]) and string slices (&str) are not FFI-safe — they are fat pointers whose layout has no stable C ABI — so ptrify() refuses them and demands the canonical (ptr, len) pair. The function itself can keep ergonomic types internally; only the exported signature must use raw parts.

Source

Thrown at src/codegen/generate-host-exports.ts:134

  line: number;
  fnName: string;
  modPath: string; // `crate::…` (relative to bun_runtime) or `bun_jsc::…`
  params: Param[];
  ret: string;
  shape: "host" | "lazy" | "generic" | "rust";
  isUnsafe: boolean;
}

const markerRe = /^\s*\/\/\s*HOST_EXPORT\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*(?:,\s*(jsc|c|rust))?\s*\)\s*$/;
// `pub fn name(` — capture name; the param list and return type are pulled by
// a small balanced-paren scanner because params routinely span lines.
const fnHeadRe = /^\s*pub\s+(unsafe\s+)?fn\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/;

function ptrify(ty: string): { cTy: string; deref: (n: string) => string } {
  ty = ty.trim();
  // `&[T]` / `&str` are NOT FFI-safe; reject (caller should use ptr+len).
  if (/^&\s*(?:\[|str\b)/.test(ty)) {
    throw new Error(`slice/str param \`${ty}\` is not FFI-safe; pass (ptr, len)`);
  }
  // `&mut T` / `&T` — keep as a reference in the thunk signature. `&T` and
  // `*const T` (resp. `&mut T`/`*mut T`) are ABI-identical for `extern "C"`
  // when the C++ caller guarantees non-null (it does), so the thunk param can
  // be the safe reference type directly and the body needs no `unsafe` deref.
  if (/^&/.test(ty)) return { cTy: ty, deref: n => n };
  // Already a raw pointer / scalar / `Option<…>` / `JSValue` — pass through.
  return { cTy: ty, deref: n => n };
}

function parseParams(list: string, where: string): Param[] {
  // Split on top-level commas (ignore `<…>` and `(…)` nesting).
  const parts: string[] = [];
  let depth = 0,
    start = 0;
  for (let i = 0; i < list.length; i++) {
    const c = list[i];
    if (c === "<" || c === "(" || c === "[") depth++;

View on GitHub (pinned to 8c5296ac45)

Solutions

  1. Change the parameter to a raw pointer plus length: `ptr: *const u8, len: usize` (or `*const T, usize`)
  2. Reconstruct the safe view inside the body with std::slice::from_raw_parts / std::str::from_utf8 (the caller — generated C++ — guarantees validity)
  3. Keep the ergonomic `&[u8]` API as a separate inner function the FFI wrapper calls

Example fix

// before
// HOST_EXPORT(BunReadPacket)
pub fn read_packet(buf: &[u8]) -> u32 { ... }

// after
// HOST_EXPORT(BunReadPacket)
pub fn read_packet(ptr: *const u8, len: usize) -> u32 {
    let buf = unsafe { std::slice::from_raw_parts(ptr, len) };
    ...
}
Defensive patterns

Strategy: validation

Validate before calling

// scan HOST_EXPORT-marked fns for slice/str params before building
import { Glob } from "bun";
for (const f of new Glob("src/**/*.rs").scanSync(".")) {
  const lines = (await Bun.file(f).text()).split("\n");
  for (let i = 0; i < lines.length; i++) {
    if (/\/\/\s*HOST_EXPORT\(/.test(lines[i])) {
      // crude: check the following fn signature lines for &str / &[ params
      const sig = lines.slice(i, i + 6).join(" ");
      if (/&\s*(?:\[|str\b)/.test(sig)) throw new Error(`${f}:${i + 1} HOST_EXPORT has slice/str param — use (ptr, len)`);
    }
  }
}

Type guard

// in generator-side TS: narrow a parsed param type before emitting a thunk
function isFFISafeParamType(ty: string): boolean {
  return !/^&\s*(?:\[|str\b)/.test(ty.trim());
}

Prevention

When it happens

Trigger: Annotating a `pub fn` with `// HOST_EXPORT(name)` (or the jsc/c variants) where any parameter is typed `&str`, `& [u8]`, `&[T]`, or `&mut [T]`, then running codegen/build.

Common situations: New Rust host functions written Rust-idiomatically first and marked for export second; refactors that change a `*const u8, len: usize` pair back into `&[u8]` for convenience.

Related errors


AI-assisted analysis of oven-sh/bun@8c5296ac45 (2026-08-16). Data as JSON: /api/errors/46f08c47eb9f6380. Report an issue: GitHub.