Skip to the content.

C → Rust idioms

One row per settled idiom. Add a row when the idiom is decided, in the same commit as its first use. Columns: what OpenBSD C does | what we write | why.

C (OpenBSD) Rust (here) Why
int f(...) returning 0 or an errno fn f(...) -> Result<T, Errno> the type carries the contract; ? replaces goto out
errno values (EINVAL, ERESTART = -1, EJUSTRETURN = -2) #[repr(i32)] enum Errno in sys/sys/errno.rs, same names and values ABI-visible numbers; names grep across both trees
LIST_HEAD(name, type) plus the field argument of every LIST_* macro (queue.h; same for SLIST, SIMPLEQ, XSIMPLEQ, TAILQ, STAILQ) ListHead<A> with A a zero-sized Adapter from queue_adapter!(A: Elem, field => ListEntry<Elem>); the element embeds ListEntry<Elem>; readers are safe, mutators unsafe with the C precondition as contract the field is fixed once and type-checked; O(1) unlink, one element in several lists, no allocation, as in C
SPLAY_GENERATE, RB_GENERATE, RBT_GENERATE[_AUGMENT](name, type, field, cmp[, aug]) (tree.h) tree_adapter!(A: Elem, field => RbtEntry, cmp[, augment = aug]) with cmp returning Ordering; heads SplayHead<A>, RbHead<A>, RbtHead<A>; RB_* and RBT_* share the kern/subr_tree.rs algorithm the comparator and the hook belong to the adapter, as struct rb_type already bundles them in C; one red-black implementation instead of a macro copy
int cmp(a, b) returning negative, zero or positive fn cmp(a: &T, b: &T) -> Ordering the type says the three outcomes; match checks them all
s = splhigh(); ... splx(s); let _s = IplGuard::raise(Ipl::High); drop restores; a forgotten splx is impossible
mtx_enter(&m); ... mtx_leave(&m); let g = m.lock(); on the ported Mutex<T> (kern_lock.c, IPL-aware) data lives inside the lock; the guard proves it is held
KASSERT(x) / KDASSERT(x) kassert!(x) / kdassert!(x) behind features diagnostic / debug same text: kernel diagnostic assertion "x" failed: file "f", line n
panic("fmt", ...) panic!("fmt", ...) routed to kern::subr_prf::panic identical message; #[allow(clippy::panic)] with a reason at each site
printf(9), log(9), %b kprintf!, kprintln!, log!(LOG_x, ...); a Bitmask(value, "\20\1FLAG...") Display helper for %b Rust format strings; %b has no counterpart so it is a wrapper
#ifdef OPTION #[cfg(feature = "option")] one knob per option(4), visible in Cargo.toml
struct proc *p passed down (borrowed) &Proc no ownership transfer
struct proc * in lists or long-lived Cell<*const Proc> inside the list entries, &Proc at the API; Arc<Process> after M3 for refcnt(9) the link is a raw pointer under the list’s lock; every borrow is explicit
fields mutated under a lock through a shared pointer UnsafeCell<T> field with doc /// Protected by: <lock> states the invariant the C only implies
vaddr_t, paddr_t, vsize_t, psize_t Vaddr(usize), Paddr(usize), Vsize(usize), Psize(usize) newtypes the compiler stops VA/PA mix-ups
#define FOO_X 0x1 flag groups bitflags! { struct FooFlags: u32 { const X = 0x1; } } typed, same names, same bits
volatile MMIO read_volatile / write_volatile behind bus_space-shaped accessors no plain dereferences of device memory
linker symbols (end, etext) unsafe extern "C" { static __kernel_end: u8; } + addr_of! address-only symbols, never read as values
a library’s symbols the program that links it must define (libsa’s file_system[], devsw[], constab[], devopen(), _rtt(); boot(8)’s machdep(), run_loadfile(), cmd_machine[]) and the #ifdefs that pick them (MDRANDOM, BOOT_STTY) one 'static table of fn pointers and slices (libsa::stand::SaConf, boot::boot::BootMd) the program registers at its entry; an optional routine is an Option<fn> the library is a crate of its own and cannot name the program’s items; a table keeps the C’s one-definition-per-program shape without extern symbols and unsafe calls
goto out cleanup ?, RAII guards, labelled blocks same control flow, enforced release
.S files real .S file + global_asm!(include_str!("x.S")) diffable against OpenBSD’s
static globals static X: Mutex<T>, atomics, or StaticCell<T> (documented init-once) static mut is forbidden
caddr_t, void * buffers &[u8] / &mut [u8]; NonNull<u8> only at ABI edges the length travels with the pointer
C strings (char *, not UTF-8) &[u8] NUL-terminated or &CStr; never &str in kernel ABI kernel strings are bytes
size_t / ssize_t usize / isize same width, same meaning
<machine/param.h>, <machine/_types.h> constants (PAGE_SIZE, KERNBASE, _ALIGNBYTES) associated consts of machine::MachineParam, defined per arch in arch/<arch>/include/{param,_types}.rs, re-exported by sys::param generic code never names an arch; the compiler proves every arch defines the whole set
typedef __int32_t pid_t; and the other scalar typedefs of <sys/types.h> pub type Pid = i32; in sys/sys/types.rs: CamelCase, no _t the _t is C namespace hygiene; the alias keeps the width and the doc comment names the C type
u_int, u_long, u_int32_t, quad_t, int8_t, … u32, u64, u32, i64, i8, …: no alias the primitive already states the width; both archs are LP64
unsigned arithmetic the C lets wrap (uint64_t sums of times that can step back, sequence numbers, ages compared modulo 2^n) wrapping_add/wrapping_sub at that site; plain +/- where the C’s own checks rule the wrap out the debug build’s overflow check would panic where the C is defined to wrap and goes on
#define NAME 42 in a header pub const NAME: usize = 42; in that header’s module, with the narrowest honest type (usize for sizes and counts, i32 where C passes an int) names stay grep-able; the type documents the use
function-like macros in capitals: ALIGN(p), MAXCOMLEN-style arithmetic, howmany(x, y), ctod(x) snake-case const fn in the same module: align, howmany, ctod untyped macros buy nothing in Rust; lowercase is the only change
statement macros that expand to a do { } while (0) calling machine-dependent accessors with a hidden sc argument (MC146818_GETTOD(sc, regs), MC146818_PUTTOD) a free function taking the accessors as impl Fn closures: mc146818_gettod(regs, read), mc146818_puttod(regs, read, write); the softc is whatever the closures capture the macro is textual glue over mc146818_read/write; closures state the same dependency and let a host test drive the loop with a fake chip
MIN/MAX, offsetof, nitems, SET/CLR/ISSET core::cmp::{min, max}, core::mem::offset_of!, .len(), \|= / &= ! / & already in core
const char *s string argument &[u8] ending at the first NUL or at s.len(), whichever comes first no read past the slice is possible; a NUL-free slice is still a string
int f(...) that is really a boolean (timingsafe_bcmp returns 0 or 1) -> bool, the doc stating true where C returns nonzero if f(..) reads the same in both languages
constant tables (crc32c_lookup[]) const TABLE: [u32; 256] = build_table(); computed by a const fn from the defining polynomial; just test-ref compares every entry with the C header the data is reproduced, not copied; the reference test keeps it honest
static __inline wrappers around one instruction (inb, outb, intr_disable, disable_irq_daif) #[inline] pub fn (or pub unsafe fn when the instruction has a precondition) with core::arch::asm!, options(nomem, nostack, preserves_flags) only where each is true the compiler inlines as GCC did; the options let it schedule around the instruction honestly
int c characters in the console and printf paths (cnputc(int), kputchar(int, ...), cn_putc(dev_t, int)) i32, with i32::from(b'x') at the call sites the C contract (-1, 0177, a NUL that is skipped) is an int’s; a u8 would hide the sentinel values
printf("%s", cstr) of a NUL-terminated byte string Str(&bytes) (kern::subr_prf::Str), a Display adaptor that stops at the first NUL kernel strings are bytes, Rust format strings want Display
a table of function pointers (struct consdev, struct bus_space, arm_clock_func) a struct of fn pointers (Option<fn> where the C allows NULL), a static instance per device the C design is a vtable; Rust spells it the same way, with no trait object or allocation
calling into a subsystem that is not ported yet unported!("uvm_map") (sys/kern/unported.rs): prints once per site, yields Errno::ENOSYS the gap is visible on the serial console and in the message buffer, and propagates as an error where the C returned one
bus_space_tag_t / bus_space_handle_t the arch’s Tag/Handle associated types of machine::BusSpace, aliased as BusSpaceTag/BusSpaceHandle; bus_space_map is unsafe (the caller vouches for the device), reads and writes through a handle are safe a handle only comes from a map; the one unsafe is where the firmware’s word is taken
__builtin_frame_address(0) Machine::frame_address(), an #[inline(always)] asm! reading rbp/x29 no stable intrinsic; inlining keeps it the caller’s frame
paddr_t holding a page frame number (atop() results, vm_physseg.start, uvm_pmemrange.low) usize; byte addresses stay Paddr the newtype keeps bytes and frames apart; frame arithmetic is plain integer arithmetic
pg + n, pg - n over a vm_page array (uvm_pmemrange.c) VmPage::add(n) / sub(n), unsafe with the one-array invariant; ptr_add(n) returns a pointer for comparisons only the C relies on the pages of one segment being one array; the unsafe names that invariant, and a comparison may point one past the end
KASSERT(e) / KDASSERT(e) without DIAGNOSTIC / DEBUG kassert! / kdassert! keep e inside a closure that is never called nothing is evaluated, as in C, but e still type-checks and the names it uses do not become “unused” when the feature is off
struct uvmexp counters bumped from everywhere (uvmexp.free++) one static UVMEXP: Uvmexp whose fields are AtomicI32, Relaxed the C updates them racily under assorted locks; atomics are the honest spelling and need no static mut
int/long struct fields mutated through a shared pointer under a lock (pmr->nsegs++, pg->fpgsz) Cell<T> for Copy fields, the lock named in the doc comment; UnsafeCell only for non-Copy data a &self API like the C’s; Cell never hands out references, so the lock is the only invariant
static struct vm_physseg vm_physmem[VM_PHYSSEG_MAX]; int vm_nphysseg; StaticCell<[VmPhysseg; N]> written only before uvm.page_init_done through vm_physmem_mut() (unsafe), read through vm_physmem() boot-time tables are written once on one CPU; the one unsafe states when
struct pool_item / struct kmem_freelist overlaid on free memory #[repr(C)] structs of Cells and a queue entry, reached with &*(ptr as *const T) any bit pattern is a valid value of such a struct, so the free block’s old contents are harmless; the Cells keep the API &self
union pool_lock { struct mutex prl_mtx; struct rwlock prl_rwlock; } PoolLock { prl_mtx: Mutex, prl_rwlock: Rwlock }, both present; the pool_lock_ops table (pr_lock_ops, chosen by PR_RWLOCK) uses one (M11a) a Rust union of two non-Copy lock types needs ManuallyDrop and unsafe access for no gain; the extra lock is a few words per pool
void * returned by an allocator (malloc, pool_get, km_alloc) Option<NonNull<u8>> NULL is None; the caller casts to its type at the one place it knows it
a GlobalAlloc for alloc::* kern/rust_alloc.rs over malloc(9)/free(9) with M_TEMP, the size raised to the alignment the kernel allocator stays OpenBSD’s; Rust’s containers are a client of it, not a second allocator
whole-file assembly with assym.h symbols (vector.S, locore.S, exception.S: TF_RIP, GSEL(...), T_PAGEFLT) the .S file next to the module, included by global_asm!(include_str!("x.S"), NAME = const offset_of!(..), ...); the file writes {NAME} where C wrote the macro the assembly stays diffable against OpenBSD’s, and the offsets come from the Rust struct, so genassym has nothing to generate
C functions called from assembly (kerntrap, do_el1h_sync, arm_cpu_irq) #[unsafe(no_mangle)] pub extern "C" fn name(frame: &mut Trapframe) the symbol name is the C’s, the ABI is the platform’s, and a &mut to a #[repr(C)] frame is FFI-safe
bit-field descriptors (struct gate_descriptor, mem_segment_descriptor, sys_segment_descriptor) filled field by field (setgate, set_mem_segment) #[repr(C)] structs of u64 words with a const fn pack(...) that places every field; the set* functions call pack Rust has no bit-fields; the packer is one place where the layout is written down and unit-tested against known descriptors
READ_SPECIALREG(reg) / WRITE_SPECIALREG(reg, val) read_specialreg!("esr_el1") / write_specialreg!("mdscr_el1", v), macro_rules! taking the register name as a string literal asm! needs the register name at compile time, as the C’s __STRING(reg) does
curcpu() (%gs:ci_self, mrs tpidr_el1) and struct cpu_info fields the CPU mutates fn curcpu() -> &'static CpuInfo with Cell/AtomicU32 fields; ci_panicbuf is an UnsafeCell the per-CPU struct is shared by every path on that CPU; Cell keeps the API &self, the atomics are the fields other CPUs read
db_regs_t ddb_regs (the trap frame the debugger works on) static DDB_REGS: StaticCell<Trapframe>, written by db_ktrap on the CPU in the debugger, read by db_trace/db_trap while db_active the C’s global is written once per debugger entry on one CPU; the unsafe accesses name that window
a C global the assembly reads by name (i8259_imen, intr_shared_edge, uvmexp, local_apic) a Rust static with #[unsafe(no_mangle)] or #[unsafe(export_name = "...")], atomic or UnsafeCell inside the .S file keeps the C’s symbol and the Rust side keeps a safe type; both name one object
struct mutex with mtx_enter/mtx_leave under a static MUTEX_INITIALIZER static M: Mutex = Mutex::new(IPL_X), mtx_enter(&M)/mtx_leave(&M) (kern/kern_lock.rs); the owner is Machine::curcpu_ptr() the C API is &self; the owner field compares CPUs, so an opaque pointer is enough for every arch
void * handle returned by *_establish and taken back by *_disestablish/*_schedule Option<NonNull<T>> out, NonNull<T> in, with disestablish unsafe (it frees) the caller cannot forge a handle; the one unsafe is where the memory goes away
.macro-style C macros that generate assembly per instance (INTRSTUB(name, num, ...), i8259_asm_mask(num)) GAS .macro blocks in the .S file, invoked once per instance; the C’s name##num is \name\()\num the file stays one assembly source, diffable with OpenBSD’s, with no preprocessor
a device tree node (void * into the blob) and an OF_* handle (int offset) FdtNode = *const u32 (null is NULL) and i32 handles, both kept; property values come back as &'static [u8] slices into the blob, None for the C’s -1 the blob lives forever and is only read, so slices carry the length the C returned separately
char *name arguments compared with strcmp (fdt_node_property(node, "reg", ...)) &[u8] without the NUL (b"reg"), compared with the blob’s NUL-terminated strings byte-string literals are the kernel’s strings (Str), and no NUL has to be appended at every call
struct cpu_info members that generic code reads (curcpu()->ci_queue, ci_schedstate, ci_randseed) and the CLKF_* macros over struct clockframe associated types Cpu::CpuInfo/Cpu::ClockFrame with accessor methods (Machine::ci_queue(ci), Machine::clkf_usermode(frame)), re-exported as machine::cpu::CpuInfo each arch defines its own cpu_info, as in C; generic code names the member through the contract instead of the arch
timercmp(a, b, <), timespeccmp, bintimecmp the derived Ord on Timeval/Timespec/Bintime (seconds first, then the fraction): a < b the lexicographic derive is exactly the macro’s two-step comparison
membar_producer() / membar_consumer() around a generation-checked read (timehands, pc_lock, cq_gen) fence(Ordering::Release) / fence(Ordering::Acquire) with the generation in an atomic and the payload in Cells the C’s barriers map to fences; the atomic generation is the only word two sides race on
a .c global of function-pointer type that autoconfiguration retargets (initclock_func, startclock_func) static F: StaticCell<fn()> with a setter (set_initclock_func) a write-once-at-boot pointer, like the other StaticCell uses; the setter names the one writer
a per-arch struct embedded in a generic one (struct mdproc in struct proc, struct pcb in struct user) whose instance is a static (proc0, proc0paddr) associated types on the machine::proc::MachineProc trait plus associated constants as initialisers (const PCB_INIT: Self::Pcb); Proc::new() is a const fn that uses them a trait cannot have const fns, but an associated const is usable in a static initialiser, so proc0 stays a static as in C
a C global list head (allproc, kthread_q) a static newtype around the head (ProcListHead(ListHead<..>)) with unsafe impl Sync stating the lock the heads hold Cells; the wrapper is where the locking claim is written down
a C identifier that is a Rust keyword (yield(), a field gen, struct pipebuf’s in) a raw identifier for a function, type or structure member the C names (r#yield, pipe_buffer.r#in); a local variable is just renamed (generation) the OpenBSD name survives for anything a caller spells; a local is nobody’s API
a function pointer handed to assembly to call later (sf_r12 = func in cpu_fork, blr x19 in proc_trampoline) the fn(*mut c_void) is stored as a *const () in the switch frame and the assembly passes it, with its argument, to a Rust unsafe extern "C" fn proc_trampoline_run that transmutes it back and calls it Rust fn pointers have no C calling convention, so the assembly never calls one directly; the one transmute is the inverse of the one cast
an MD int that MI code declares extern and every machdep.c defines identically (cold, safepri, physmem) one static in sys/sys/systm.rs (COLD, SAFEPRI, PHYSMEM), which the archs write nothing per arch differs; a trait method would be three copies of the same line
proc0paddr: the u-area locore reserves in .bss for proc0 (USPACE bytes, the struct user at the bottom) static PROC0_UAREA: Uarea per arch, Uarea being #[repr(C, align(4096))] { u: User, _stack: [u8; USPACE - size_of::<User>()] } in sys/sys/user.rs cpu_fork copies the parent’s trap frame from the top of its u-area, so proc0’s must be a real USPACE block even though it boots on the loader’s stack
syscalls.master run through makesyscalls.sh into syscall.h, syscallargs.h, init_sysent.c, syscalls.c cargo xtask gen-syscalls (tools/xtask/src/syscalls.rs) writes sys/sys/syscall.rs, syscallargs.rs, kern/init_sysent.rs, kern/syscalls.rs; --check runs in just ci the generated files are committed like the C’s; the table points unported syscalls at sys_nosys, found by scanning for pub fn sys_*
syscallarg(x) (a register_t-wide union per argument) and SCARG(uap, k) Syscallarg<T> union with .get(), the sys_*_args structs as #[repr(C)] with one slot per argument, sysargs::<T>(v) for the cast the arguments are the trap frame’s registers in a row, so the struct must be register-wide per field; get reads the low bytes as the C’s le.datum
int sys_foo(struct proc *, void *, register_t *) fn sys_foo(p: &Proc, v: &SysArgs, retval: &mut [Register; 2]) -> Result<(), Errno> (SyCall in sys/systm.rs; SysArgs is the six argument registers, sysargs::<SysFooArgs>(v) the safe SCARG view) the errno and ERESTART/EJUSTRETURN go through Errno; the void * becomes a reference to the register block the MD entry already has, so no syscall needs unsafe to read its arguments
the user/kernel copy routines in assembly with pcb_onfault (copyin, copyout, copyinstr, copyoutstr, kcopy) the .S stays assembly (global_asm!), the Rust entry points take the kernel side as a slice (copyin(uaddr: usize, kbuf: &mut [u8])) and only kcopy is unsafe the only thing that can be wrong is the user address, which the assembly checks and whose fault it catches; the slice carries the length the C passed separately
setjmp/longjmp around a call that may fault (efi_enter_check and efi_fault of arm64 efi_machdep.c) an assembly trampoline (global_asm!) saves the callee-saved registers, the stack pointer and the return address in the C’s label_t, then makes the call; the pcb_onfault target restores them and returns from the trampoline with a “faulted” flag, which the Rust caller turns into EFAULT a function that returns twice cannot be called from Rust soundly; the trampoline keeps the C’s recovery point and buffer without a second return into compiled Rust code
ddb’s db_error(msg), which longjmps to db_recover (the setjmp in db_command_loop) from the lexer, the expression parser or a command db_error(Some(msg)) prints, flushes the lexer and returns a DbError; every function that can reach it returns DbResult<T> (Result<T, DbError>) and propagates with ?; db_command_loop drops the Err where the setjmp was. db_more’s q, deep inside db_printf, sets DB_QUIT instead, which silences ddb output until the next prompt no unwinding past Rust frames; the error path is visible in every signature, and the loop is the one recovery point the C has
an ELF header read out of a file with vn_rdwr into a malloced Elf_Ehdr/Elf_Phdr elf_read_from(p, file, off, &mut buf) (vn_rdwr into a byte buffer, ENOEXEC on a short read), then read_image!(ElfEhdr, &buf, off): a bounds-checked ptr::read_unaligned out of the bytes, None when short the structures are #[repr(C)] integers, valid for every bit pattern, so the one unsafe is the unaligned read; the slice bounds replace the C’s resid check
caddr_t/vaddr_t fields that hold user addresses (vm_taddr, ps_strings, ev_addr) usize in the structures, Vaddr only at the machine contract (pmap_enter, setregs) a user address is a number the kernel never dereferences; the newtype marks the places where an architecture does
sys_execve for the one caller that has the executable in memory (start_init with a boot module, while there is no root file system) exec_image(p, path, argp, envp, image): sys_execve’s body with ep_image set, check_exec skipping namei and the vnode checks; Err(EJUSTRETURN) is success, as the syscall’s the name is this port’s (the C has no such function) and goes away when init is a file; everything after the lookup, arguments included, is the syscall’s
the executable’s struct vnode * in the exec package and the vmcmds (ep_vp, ev_vp), which the boot module stands in for ExecFile<'a>: Vnode(&'static Vnode) or Image(&'a [u8]); new_vmcmd vrefs and kill_vmcmds vreles only the vnode the loaders and vmcmds keep the C’s shape with one match where they read or map the file, and the image can never be mistaken for a vnode
a malloced array kept as a pointer and a count in a structure (pn_pins/pn_npins, ep_pins/ep_npins) the pointer in a Cell<*mut u32> or Option<NonNull<u32>> beside the count; unsafe fn pins_slice(ptr, n) makes the slice where the C indexes it, free takes the pointer back the table outlives every borrow (it moves from the exec package to the process and is freed by exec or exit), which no lifetime can say; the one unsafe names the pairing
struct rwlock’s volatile unsigned long rwl_owner holding a struct proc * or a reader count plus flag bits AtomicUsize: the writer’s *const Proc as an integer with RWLOCK_WRLOCK or’ed in (rw_self), the readers shifted by RWLOCK_READER_SHIFT; rw_enter returns Result<(), Errno> and rw_assert_* are empty without feature diagnostic the C’s rw_cas on the same word; compare_exchange on one CPU is the plain compare-and-store the !MULTIPROCESSOR build has
vm_page_t *pps arrays whose slots hold NULL, a page or the sentinel PGO_DONTCARE ((struct vm_page *)-1) (pgo_get, uvm_fault) &mut [*const VmPage] with PGO_DONTCARE as usize::MAX as *const VmPage and pgo_dontcare(p); functions that only read pages take &[Option<&VmPage>] (uvm_page_unbusy) an Option<&T> has only “null” and “valid”, so the sentinel needs the raw pointer; the pagers write the slots in place as the C does
a struct whose first member is the generic one and is cast both ways (struct uvm_aobj { struct uvm_object u_obj; ... }, (struct uvm_aobj *)uobj) #[repr(C)] with the embedded struct first and one accessor that checks the C’s type tag before casting (fn aobj(&UvmObject) -> &UvmAobj after UVM_OBJ_IS_AOBJ) repr(C) pins the first field at offset 0, so the cast is the C’s; the tag check is the only place the unsafe lives
unions of alternatives that are never read through the inactive member (am_impl, u_swap, daddrs, dfree, object in vm_map_entry) side-by-side fields when both are small links (am_small beside the buckets, addr_entry and addr_kentry, rbtree and dfree_tailq); an enum when the C keeps a tag (VmMapEntryObject over UVM_ET_OBJ/UVM_ET_SUBMAP); a slice-or-pointer pair for u_swap a Rust union needs unsafe on every read; the extra words cost less than the audit, and the enum makes the tag check the compiler’s
a softc: struct foo_softc { struct device sc_dev; ... } allocated by config_make_softc as malloc(ca_devsize, M_DEVBUF, M_NOWAIT\|M_ZERO) and cast both ways ((struct foo_softc *)self) #[repr(C)] struct FooSoftc { sc_dev: Device, ... } of Cells (all-zero valid), marked unsafe impl Softc; ca_devsize: size_of::<FooSoftc>(); the driver gets &Device and calls unsafe { self_.softc::<FooSoftc>() } the allocation and the cast are the C’s: repr(C) puts the device at offset 0, M_ZERO makes the rest valid, and the unsafe names the one fact the compiler cannot see, which cfattach made the device
void *aux (a bus’s attach arguments, read by the child as the bus’s type) and void *match (a cfdata, or a softc below a CD_INDIRECT parent) aux: *mut c_void, cast back with &*aux.cast::<FdtAttachArgs>() under a // SAFETY: naming the bus; match is &CfMatch, an enum of Cfdata(&'static Cfdata) and Softc(SoftcMatch) with cfdata() for either the bus decides the type of aux at run time, as in C, so a raw pointer is honest; match has only two meanings and the tag (the parent’s CD_INDIRECT) is known to autoconf, so an enum makes the cast unnecessary
ioconf.c, generated by config(8) from the kernel configuration (cfdata[], cfroots[], pv[], loc[], locnames[], locnamp[], pdevnames[]) sys/arch/<arch>/conf/ioconf.rs: static CFDATA: StaticCell<[Cfdata; N + NFREE]> written with Cfdata::new(...) in config(8)’s column order (eight Cfdata::free() slots at the end), slices for the parent vectors and locators, reached through machine::autoconf (ioconf_mut once for UKC, then shared) config(8) is a host tool the project does not port; the tables are short while few drivers exist, and keeping its layout lets a future generator replace them line for line; UKC (boot -c) edits them before autoconfiguration, the vm_physmem pattern of a boot-time table
in/out pointer parameters that are prefilled or NULL (uvm_map_isavail’s **start_ptr/**end_ptr, uvm_mapent_mkfree’s **prev_ptr) &mut Option<&'m VmMapEntry>, None for the C’s NULL; plain out parameters become return values (Result<(&entry, addr), Errno>) the Option keeps the C’s “look it up unless given” contract in the type; a bare out parameter has no such contract and reads better as a return
a typed object returned by *_create, used by many calls, freed by *_destroy (taskq_create, task_add(tq, ...), taskq_destroy), with static instances beside it (systq) Option<&'static T> out, &T in, unsafe fn *_destroy(NonNull<T>); an item the object keeps linked until it runs is &'static (task_add(tq, w: &'static Task)) the static and the created objects share one safe API; the one unsafe is where the memory goes away, and taking a NonNull back means no reference argument outlives the free
a reference-counted pool object passed as a raw pointer (struct ucred * from crget/crhold, given back by crfree; struct vmspace * likewise) &'static T out of the allocator and the hold, taken back by the release (crfree(&'static Ucred)); the long-lived fields are Cell<*const T> with an accessor (p.ucred(), pr.vmspace()) that asserts non-null the object lives in a pool until its last reference goes, which no lifetime can express; &'static keeps every reader safe and the count is the C’s contract, as for uvmspace_alloc/uvmspace_free
a syscall name with a double underscore (sys___set_tcb, sys___get_tcb) the same name with #[allow(non_snake_case)] and a reason the generator finds pub fn sys_<name>( by the C’s name; renaming would break the table and the grep
struct mbuf *: a pool item every layer passes, links and frees (m_get, m_next, m_free) &'static Mbuf with Cell fields, NULL as None; links are Cell<Option<&'static Mbuf>>; the free functions that accept NULL take impl Into<Option<&Mbuf>> the C mutates mbufs through shared pointers everywhere; Cell keeps that &self API, and m_freem(m) still reads like C whether m may be NULL or not
a union whose live member a flag selects (mbuf.M_dat: m_dat, or m_pkthdr + m_ext/m_pktdat under M_PKTHDR/M_EXT) one UnsafeCell byte area of the C’s size, with accessor methods that return the typed views (m.m_pkthdr(), m.m_ext()) and raw pointers for the byte members; a structure assignment of a view is a byte copy inside the cell (m_pkthdr_assign) the views are #[repr(C)] Cells of integers and raw pointers, valid for any bytes, so no unsafe reaches the callers; MSIZE == size_of::<Mbuf>() is a compile-time check
field-alias macros (#define m_next m_hdr.mh_next, m_len, m_flags) methods with the macro’s name returning the field’s &Cell<T>: m.m_next().get(), m.m_len().set(n) the macro is an lvalue; a &Cell is the Rust lvalue, and the names grep across both trees
a callback plus its opaque state (m_apply(m, off, len, f, fstate)) a closure: m_apply(m, off, len, \|bytes\| ...), the state captured, the data a slice the caddr_t fstate is the closure’s environment; the slice carries the length the C passed separately
a header whose name is a Rust keyword (net/if.h, netinet/in.h) module and file with a trailing underscore: sys/net/if_.rs (crate::net::if_), sys/netinet/in_.rs; the .c of the same name (net/if.c) shares the module, as any .h/.c pair of one name does r#if would have to be spelled in every use path; the underscore is the only change and ports.toml records the mapping
unions at the user/kernel or wire boundary (ifr_ifru in struct ifreq, icmp_hun/icmp_dun in struct icmp) and the #define ifr_flags ifr_ifru.ifru_flags shorthands a #[repr(C)] union named after the member (IfrIfru, IcmpHun) and safe accessor methods (ifr.ifr_flags(), ifr.set_ifr_flags(f), icp.icmp_id()) whose one unsafe read is sound because every member is plain data, valid for any bit pattern the layout is the ABI, so side-by-side fields (the row above for kernel-internal unions) would change the size; the accessors keep the C spelling and the unsafe in one place
bit-fields in wire headers (u_int ip_hl:4, ip_v:4, declared in opposite orders for little- and big-endian machines) one u8 field (ip_vhl) with getters and setters that shift and mask (ip.ip_hl(), ip.set_ip_v(4)) the C reorders the fields so that the byte on the wire is the same on every machine; the explicit shifts are that byte everywhere, with no cfg(target_endian)
network byte order: wire fields (ip_len, sin_port, s_addr, ether_type) and the kernel’s __IPADDR(x) = htonl(x) fields keep the C’s u16/u32 and hold network-order values, as in C; htons/htonl/ntohs/ntohl are const fns in sys/sys/endian.rs, so INADDR_LOOPBACK is htonl(0x7f000001) computed at compile time; protocol constants (ETHERTYPE_IP, IP_DF) stay host order the C code compares and copies addresses without swapping; a newtype per byte order would put a conversion on every line of ip_input that the C does not have
satosin(sa), sintosa(sin), satosdl(sa), sstosa(ss): inline casts between the sockaddr flavours const fn pointer casts (*mut Sockaddr → *mut SockaddrIn); dereferencing stays the caller’s unsafe the flavours differ in size (sockaddr_dl is 32 bytes, sockaddr 16, and a sockaddr_dl may be longer still) and alignment, so a cast between references would claim memory the reference does not cover
_IOW(g, n, t), _IOR, _IOWR (sizeof(t) of a type argument) _iow::<T>(b'g', n): a generic const fn using size_of::<T>() the type stays checked and the command is computed at compile time, as the C’s constant expression is
#define constants that a structure field stores (AF_* in sa_family, ICMP_* in icmp_type, RTF_* in rt_flags, RTM_* in rtm_type) the field’s type (SaFamily, u8, u32, u8); constants that index arrays (RTAX_*) are usize the common use is a comparison with the field, which then needs no cast; a function that takes the C’s int converts with i32::from
struct file * with FREF(fp) / FRELE(fp, p) and fdrop on the last reference &'static File out of falloc/fd_getfile (Option for the C’s NULL), fref(fp), frele(fp, p) with p: impl Into<Option<&Proc>> the crget/crfree idiom for a pool object every table and thread holds a count on; frele accepts a thread or None as the C accepts NULL from the socket garbage collector
struct uio { struct iovec *uio_iov; int uio_iovcnt; ... } stepped with uio->uio_iov++; uio->uio_iovcnt-- Uio<'a> { uio_iov: &'a mut [Iovec], ... }, stepped by re-slicing (mem::take then &mut iov[1..]); uio_iovcnt() is the length; uiomove(cp: &mut [u8], uio) the count travels with the array, and an exhausted uio cannot be indexed past its end
arrays a structure points at and replaces when it grows (fd_ofiles, fd_ofileflags, fd_himap, fd_lomap, swapped by fdexpand) the raw pointers stay in Cells; every read and write goes through a bounds-checked index accessor that returns or takes a value (fdp.ofile(fd), fdp.set_lomap(off, v)) no reference into an array can outlive the free of an expansion, so the one unsafe per accessor is local; the bound is the C’s length (fd_nfiles, NDLOSLOTS(fd_nfiles))
copyin/copyout of a small ABI structure (struct stat, struct flock, struct iovec) Stat::to_bytes(), Flock::from_bytes()/to_bytes(), Iovec::from_bytes(): the fields placed at their offset_of! positions in a byte array, padding zeroed no unsafe view of a struct as bytes, and no uninitialised padding byte reaches user space
copyin(uaddr, &sa, sizeof(sa)) / copyout(&sa, uaddr, sizeof(sa)) of an ABI structure (struct sigaction, siginfo_t, struct sigcontext, struct timespec) copyin_obj::<T>(uaddr) / copyout_obj(&t, uaddr) (machine::copy) for T: AbiPod, an unsafe trait implemented only by #[repr(C)] integer structures with no implicit padding; a hole the C compiler leaves is a named field (_pad, hole) the one unsafe is the marker, where the layout is checked; every copy site is safe, and no uninitialised padding byte can reach user space
a user-space function pointer the kernel stores but never calls (sig_t, sa_handler) and union sigval usize (Sig, with SIG_DFL/SIG_IGN/SIG_ERR as 0, 1, -1); Sigval a one-word newtype whose sival_int is the low half the kernel only compares and copies these words; a Rust fn type would claim a callable kernel function
a union read through member macros (siginfo_t’s _data with si_pid, si_addr, si_status, …) a u64 word array of the union’s size with getter/setter methods named after the macros (si_addr(), set_si_addr()), offsets as in C the members overlap as in C (a test pins which words they share) and no union read needs unsafe
a system call implemented in machine-dependent code (sys_sigreturn in machdep.c/sig_machdep.c) the machine’s function behind a trait method (MachineSignal::sys_sigreturn), and a one-line pub fn sys_sigreturn in the MI file that forwards to it the generated table only finds pub fn sys_* under sys/kern and sys/uvm; the forwarder keeps the C’s name in the table and the body in the arch tree
a system call in a driver file (sys_getentropy in dev/rnd.c) a plain pub fn sys_getentropy in sys/dev/rnd.rs; the syscall generator scans the files directly under sys/kern, sys/uvm and sys/dev the function stays where the C has it; no forwarder is needed for a machine-independent file
nullable out-parameters of a helper (dowait6’s int *statusp, struct rusage *rusage, siginfo_t *info) Option<&mut T> (Option<&T> for a Cell-based structure such as Rusage), None for NULL the caller’s “do I want this” is the Option; the helper fills only what it was given, as the C tests each pointer
a waiter’s structure on its own stack, linked into a shared list until a waker takes it off (struct futex in futex_wait) a local with a TailqEntry, inserted and removed with the list’s unsafe mutators under its lock; the members the waiter reads without the lock (ft_proc, ft_fsq) are atomics the C’s ownership rule (the lock holder owns the entries until it clears ft_proc) is what makes the stack lifetime sound; the unsafe blocks state it at each list operation
a sysctl handler (int *name, u_int namelen, void *oldp, size_t *oldlenp, void *newp, size_t newlen, struct proc *p) and its helpers (sysctl_int, sysctl_rdstring, …) (name: &[i32], oldp: usize, oldlenp: &mut usize, newp: usize, newlen: usize, p: &Proc) -> Result<(), Errno> (Sysctlfn); oldp/newp are user addresses, 0 for NULL; a writable char *str, size_t maxlen is &mut [u8] the name’s length travels with it; the buffers are only ever reached through copyin/copyout, which take addresses; every caller has an oldlen to lend
int *valp handed to sysctl_int/sysctl_int_bounded (a global, or a local passed by address) &AtomicI32; a local is AtomicI32::new(v) read back with into_inner() the helpers use the C’s atomic_swap_uint/atomic_load_int on it, so the atomic is the honest type for both kinds
sysctl_rdstruct(oldp, oldlenp, newp, &s, sizeof(s)) sysctl_rdstruct(oldp, oldlenp, newp, s.as_bytes()) with s: impl SysctlPlain (#[repr(C)], no padding, any bit pattern); a C structure with padding gets the hole as an explicit member (kinfo_proc.p_pad0) or is written out member by member (loadavg, vmtotal) reading padding bytes is undefined behaviour in Rust; the one unsafe impl per type is where the layout claim is made
a C global whose uppercase spelling is already a constant of the same header (hw_vendor beside HW_VENDOR, maxslp beside MAXSLP) the lowercase C name with #[allow(non_upper_case_globals)] and a one-line reason both names stay grep-able; uppercasing would collide with the constant
a source file a build script generates (vers.c from newvers.sh) build.rs reads only its environment and passes values with cargo:rustc-env; the module (conf/vers.rs) builds the strings with concat!(env!(..)) the build stays reproducible (no clock, no counter file), and the strings are consts like the C’s const char[]
bus_dma_tag_t, bus_dmamap_t, bus_dma_segment_t and the bus_dmamap_*/bus_dmamem_* macros that call through the tag each arch’s <machine/bus.h> types (&'static BusDmaTag of fn pointers, BusDmamap, BusDmaSegment) as the associated types of machine::bus::BusDma, with C-named free functions; MI code reads map.dm_mapsize, map.dm_nsegs, map.dm_segs(), seg.ds_addr, seg.ds_len by name through the aliases, which bus_dma_public_members makes every arch define; the loads are unsafe a driver reads like the C one; the archs keep their own private members and flag values; the one unsafe is the C’s contract that the memory outlives the load, because sync copies to it and cleans the caches over it
a struct ending in a variable-length array allocated with its elements (struct bus_dmamap { ...; bus_dma_segment_t dm_segs[1]; } plus the bounce-page pointers) the header and the elements in one malloc, with pointers to the elements taken from the allocation at creation (_dm_segs, _dm_pages) and slice accessors (dm_segs()); the elements are Cells a Rust struct cannot end in an unsized array of Cells, and a reference to the header does not cover the bytes after it; the stored pointer does
pci_chipset_tag_t, pcitag_t, pci_intr_handle_t and the <machine/pci_machdep.h> functions associated types and methods of machine::pci_machdep::PciMachdep with C-named free functions; functions that return 0/1 and fill an out parameter (pci_intr_map*) return Option, pci_decompose_tag’s out pointers a tuple, pci_intr_string a PciIntrStr by value MI PCI code and drivers never name an arch; amd64 implements the functions, arm64 dispatches through the bridge’s machine_pci_chipset as its C macros do
a function-like macro whose lower-case name is also a C function (PCI_MAPREG_TYPE(mr) and pci_mapreg_type(pc, tag, reg)) the macro keeps its capitals as a const fn with #[allow(non_snake_case)] both C names survive and grep; the lower-case rule would merge two different things
a member pointing into a subsystem that is not ported and is therefore always NULL (pba_ioex, pa_memex, sc_busex: struct extent *) the member is left out and each place the C tests it carries a comment naming the NULL branch it takes a field that can only hold NULL would need a type for an unported struct; the comments keep the code paths visible
a driver structure that embeds the generic one first and is cast from it (struct arpcom { struct ifnet ac_if; ... }, (struct arpcom *)ifp) when the generic struct has no type tag #[repr(C)] with the generic struct first; the function that receives the outer struct (ether_ifattach(&Arpcom)) marks the inner one (Ifnet::set_arpcom, unsafe), and the cast (arpcom_of(ifp)) checks the mark and panics without it the C trusts every caller; one flag turns a silent type confusion into a panic and keeps the cast safe to call
caddr_t data of an ioctl handler (if_ioctl, ifioctl, ether_ioctl), whose type the command encodes data: *mut u8 and an unsafe fn whose # Safety is sys_ioctl’s contract (an aligned kernel copy of the command’s structure); the handler casts with &mut *data.cast::<Ifreq>() the type is a run-time property of cmd, so no Rust type can carry it; the one contract is stated once on the function pointer type (IfIoctlFn)
a struct sockaddr * argument that may be a longer flavour (if_output’s dst, ifa_ifwithaddr) *const Sockaddr and an unsafe fn (IfOutputFn); fields are read with (*dst).sa_family and bytes up to sa_len with slice::from_raw_parts a &Sockaddr covers 16 bytes only, so casting it to a sockaddr_in6 would read past the reference (the row on satosin above)
a structure that is only forward-declared until its file is ported (struct foo;) an uninhabited pub enum Foo {} with accessor methods for the members callers read, each match *self {}; arguments are Option<&Foo> every pointer to it is NULL until the port exists, which the type proves; the callers’ code is written now and compiles, and the port replaces the enum with the struct (struct rtentry went this way until net/route.c)
a struct consdev member a driver rewrites at run time (wsdisplay_set_cons_kbd sets wsdisplay_cons.cn_getc and cn_bell) the Consdev keeps fixed entry points (wsdisplay_cngetc, wsdisplay_cnbell) that call the function a StaticCell holds; the setter writes the cell Consdev’s function pointers are plain fns shared with cn_tab readers, and every other console driver fills them once; one indirection keeps that contract while the keyboard can still come and go
a protocol header read from or written into mbuf data (mtod(m, struct ip *), struct icmp *, struct ether_arp *), which need not be aligned the fixed header is copied out and back (mtod_ip(m) / mtod_ip_store(m, &ip), ea_get/ea_store); a header with variable parts is a bounds-checked view over the bytes with getters and setters at the C offsets (IcmpPkt::of(m, off).icmp_type()) a &mut Ip into a driver’s buffer would be undefined behaviour when misaligned; a 20-byte copy costs nothing next to the checksum, and the view keeps icmp_ip’s nested header at its offset
struct rtentry **prt, a pointer to the link that holds an entry (the list head or the previous entry’s rt_next) an enum of the two places (RtLink::Head / RtLink::Next(rt)) with get/set a raw pointer into a Cell would work but hides which list it edits; two cases name both
a walker with a void *arg and a function pointer (rtable_walk(..., int (*func)(struct rtentry *, void *, unsigned int), void *arg), ART_FOREACH) a closure FnMut(&Rtentry, u32) -> Result<(), Errno> that captures what arg carried the closure’s captures are typed; the iteration order and the early stop on an error stay the C’s
a tagged pointer word (ART’s heap slots: an art_node * or a subtable heap with the low bit set) a usize word (ArtHeapEntry) with const fn helpers named after the C’s (art_heap_entry_is_node, art_heap_entry_to_heap, art_heap_to_heap_entry) the tag is a property of the word, not of a type; reading it as an integer first keeps the cast explicit
ifatoia(ifa), a cast from the generic struct ifaddr * to the protocol’s struct in_ifaddr * that embeds it first #[repr(C)] with ia_ifa first and a checked ifatoia that tests ifa_addr’s family (AF_INET) before the cast and panics otherwise the family is the C’s own type tag; the check turns a type confusion into a panic rather than a wild read
a call into an unported function whose failure ends the C function, with the C’s code after it (error = rtable_add(...); if (error) return error;) let error: Result<(), Errno> = Err(unported!("rtable_add")); error?; followed by the rest of the C the gap is reported and propagated, and the code after it stays ported instead of becoming a comment; the compiler does not see the rest as unreachable, so it keeps type-checking
struct cpumem * counters (counters_alloc, counters_inc, counters_pkt) on one CPU &'static [AtomicU64; N] from malloc, bumped with relaxed atomics (if_counters) as uvmexp: written before <sys/percpu.h> was ported. M11e moved mbstat and evcount to per-CPU counters as in C; uvmexp and if_counters stay shared relaxed atomics, which is correct under MULTIPROCESSOR and only loses the C’s per-CPU spreading
struct cpumem * from <sys/percpu.h> (cpumem_get, cpumem_malloc, counters_alloc), M11a CpumemPtr, a Copy handle of the slot array (MP) or the memory (UP) plus the per-CPU size; per-CPU memory reached as bounds-checked slices; counters are AtomicU64 bumped with relaxed fetch_add, the MP generation number with release/acquire the C indexes raw memory unchecked; the size makes the CPU and counter indexes checkable; fetch_add keeps an interrupt on the same CPU from tearing an increment
a per-CPU structure whose counters other CPUs read under a generation number (struct pool_cache’s pc_gen, pc_nget) owner-only Cells for what only the owner reads, AtomicU64 with relaxed store(load() + 1) for the counters, pc_gen stored with release and loaded with acquire the C relies on plain loads and stores at raised IPL; atomics make the cross-CPU reads defined
struct vnode * (getnewvnode, vget/vref/vrele/vput) and a file system’s private node behind v_data &'static Vnode: a vnode_pool item that is never freed (recycled through the free lists), Cell members; v_data stays Cell<*mut c_void> and the file system reads it with unsafe fn data::<T>() the C never frees a vnode, so 'static is the truth and every borrow is safe; the use count is the C’s contract, as for struct file
struct vops of int (*vop_x)(void *) called with a struct vop_x_args * (VOP_LOOKUP, …) Option<fn(&mut VopXArgs<'_>) -> Result<(), Errno>> per slot (None = NULL = EOPNOTSUPP), the struct vop_*_args as Rust structures; helpers the C installs in many slots (nullop, vop_generic_badop, dead_ebadf) take no arguments and are written as closures, Some(\|_\| nullop()) the argument types are checked per operation, so a file system’s body cannot read the wrong structure; a generic fn helper<A> cannot coerce to the higher-ranked slot type, a non-capturing closure can
VOP out-parameters (struct vnode **a_vpp, filled or NULLed by the file system) and the VFS_ROOT/VFS_VGET/VFS_FHTOVP ones &mut Option<&'static Vnode> in the VOP argument structures; the vfsops that only ever return a vnode return Result<&'static Vnode, Errno> a file system’s lookup or create ports with its *ap->a_vpp = ... lines unchanged; the vfsops have no partial result to keep
VOP_LOOKUP(...), VFS_ROOT(mp, &vp): the C’s uppercase function and macro names the same names with #[allow(non_snake_case)] and a reason the names grep across both trees, as sys___set_tcb does
char * cursors into a buffer (cn_nameptr, ni_next in cn_pnbuf; getcwd’s char **bpp into bufp) the structures keep the C’s raw pointers for the file systems; the walkers (vfs_lookup, getcwd) work on a slice and an index, and getcwd’s buffer travels as Option<(&mut [u8], &mut usize)> the index cannot leave the buffer; the pointers stay where the C’s interface (and a file system’s lookup) reads them
a function returning 0, an errno or -1 for “not here, try the other way” (cache_lookup, cache_revlookup, vfs_getcwd_getcache) Ok(Some(vp))/Ok(true) for found, Ok(None)/Ok(false) for the C’s -1, Err(e) for the errno -1 is ERESTART in Errno; the third outcome gets its own variant instead of an overloaded number
NDINIT(&nd, op, flags, segflg, path, p) with const char * that is a user or a kernel address ndinit(op, flags, NiDirp::User(uaddr) / NiDirp::Sys(bytes), p) -> Nameidata (ndinitat with a dirfd) the segment and the address are one value, so a kernel string cannot be read as a user address; the constructor returns the structure the C fills
DMA memory a device reads and writes concurrently (virtio’s descriptor table, avail and used rings) raw pointers kept in Cells, every access a read_volatile/write_volatile through accessor methods (vq.used_idx(), vq.set_avail_ring(i, v)) that check the index; never a reference into the ring a &/&mut would let the compiler assume nothing else changes the memory; the device does, and the C’s plain accesses rely on bus_dmamap_sync and barriers the compiler cannot see
virtio_membar_producer(), virtio_membar_consumer(), virtio_membar_sync() (<machine/atomic.h>) machine::atomic, a trait the machines implement with their instructions (dmb st on arm64, a compiler barrier on amd64) core’s fences only order against other CPUs (dmb ish); the device needs the machine’s full-system barrier
a transport’s softc reached from the generic one through an ops table ((struct virtio_pci_softc *)vsc in virtio_pci_ops’ functions) #[repr(C)] with the generic softc first and a cast function (psc(vsc)) that checks vsc.sc_ops is this transport’s table before casting the table is the type tag the C relies on implicitly; checking it makes the cast safe to call
struct fdt_attach_args read by a machine-independent driver (dev/fdt/virtio_mmio.c) a generic associated type of machine::fdt::Fdt (type FdtAttachArgs<'a>), aliased as machine::fdt::FdtAttachArgs; machines without a device tree give a struct with the same members; fdt_attach_args_public_members checks the names on every machine the members are read by name as in C, and no generic code names arch::arm64
#if defined(__i386__) \|\| defined(__amd64__) inside a machine-independent file an associated constant of the machine contract named after what differs (PciMachdep::PCI_MSI_PER_BRIDGE), tested with if generic code never names an architecture (.claude/rules/arch.md); the branch still compiles everywhere
a macro over a structure member name (CREAD(sc, queue_size), CWRITE(sc, memb, val)) a macro_rules! with the C’s name in lower case (cread!(sc, queue_size)) that passes offset_of! and the member’s size to a function the member is a token, which only a macro can take; the work stays in a function
the const char *what an interrupt keeps (dv_xname passed to *_intr_establish and evcount(9)) &'static str; a driver whose name lives in its softc hands a copy that is never freed (virtio_intr_name) the machines’ establish functions keep the name for the handler’s lifetime; borrowing a softc array for 'static would need an unsafe the copy avoids
a __packed structure the driver fills in DMA memory at a byte offset (struct virtio_net_ctrl_guest_offloads, struct virtio_net_ctrl_mac_tbl) #[repr(C, packed)] (alignment 1, the C’s size), written with ptr::write/write_unaligned through the raw pointer into the area; a header the device reads (struct virtio_net_hdr) is built as a local value and copied in as bytes a reference to a misaligned field is undefined behaviour in Rust; the raw writes are what the C’s stores compile to
a structure in DMA memory that one layer hands another as a pointer and both fill member by member before the device reads it (atascsi’s xa->fis->command = ... into ahci’s command table) the pointer stays raw in a Cell; the owner reads the whole structure volatile, a closure changes the copy, and it is written back volatile (xa.with_fis(\|fis\| fis.command = ...)); a byte the C stores alone (fis[15] = ...) is one volatile store (ccb.set_cfis(15, ...)) the structure is small (a 20-byte FIS), the device does not read it until the command is started, so the read-modify-write is the C’s member stores, and no reference into DMA memory exists
struct lockf_state **state: a pointer to the owner’s pointer, kept in the object (ls_owner) so the last release can clear it the slot is a Cell<Option<&'static T>> (LockfStateSlot) in the owner; functions take &LockfStateSlot, and the object keeps *const LockfStateSlot the owner (a vnode’s specinfo, an inode) outlives the object by the C’s contract (lf_purgelocks before it goes), which no lifetime can say; the one raw pointer is the back link
struct buf *: a bufpool item every layer passes, with its data mapped at b_data while B_BUSY &'static Buf with Cell members (b_flags through isset/set/clr); the data is unsafe fn data(&self) -> &mut [u8], whose contract is the C’s: the caller owns the busy, mapped buffer as struct mbuf: the C shares buffers through pointers at splbio; the busy flag, not a borrow, says who may touch the bytes, so the one unsafe is where the slice is made
int bread(vp, blkno, size, struct buf **bpp), which fills *bpp even on error (breadn, bread_cluster) bread(vp, blkno, size) -> (&'static Buf, Result<(), Errno>) the caller must brelse the buffer in both cases, so the buffer is not part of the error; a Result<&Buf, Errno> would lose it
NOLIST, a sentinel pointer written into a queue link to say “on no list” (b_vnbufs.le_next, b_freelist.tqe_next) a Cell<bool> beside the link (b_onvnbufs, b_onfreelist), set and cleared where the C inserts and writes the sentinel the links are private to sys/queue.rs; a flag says the same thing without forging a pointer
a table of function pointers over an opaque void * the table’s own constructor made (struct bufq_impl and bufq_data) the void * stays a *mut c_void/NonNull<c_void>; the operations are unsafe fns whose contract is that the pointer came from the same table’s impl_create only the table knows the type behind the pointer, so the cast lives in each operation and the caller vouches for the pairing once
struct vm_page ***ppsp_ptr / a returned struct vm_page ** pointing inside the caller’s page array (uvm_mk_pcluster, uvm_pager_put) the index of that element: -> usize, ppsp: &mut usize beside pps: &mut [*const VmPage] an index into the slice the caller owns keeps the bounds check; the C’s pointer arithmetic over the same array is the index arithmetic
a kqueue filter (filt_piperead(kn, hint)) and its struct filterops fn filt_piperead(kn: &Knote, hint: i64) -> bool reading its object through the knote (kn.fp(), kn_hook) and writing kn_data/kn_flags through the alias methods; the ops a static Filterops of Some(fn) the filter has the C’s shape, so an object’s filters port one for one and plug into kern_event.rs unchanged (until kern_event.c was ported the body returned a PipeFilter struct for it to apply)
struct cdevsw cdevsw[] / struct bdevsw bdevsw[] in each arch’s conf.c, indexed by major ((*cdevsw[major(dev)].d_open)(dev, ...)), nchrdev/nblkdev, and pluartcnattach’s cdevsw[maj] = pluartdev Devsw<Cdevsw, N>: an array of Cell<Cdevsw> per arch behind machine::conf; cdevsw(maj) returns a copy of the entry (cdev_notdef() past the end), cdevsw_set(maj, e) replaces one; nchrdev() is the length the major numbers are ABI and arch-specific, so the tables stay per arch; entries are small Copy structs of fn pointers, so a copy is the C’s read, and the one boot-time write needs no static mut
cdev_tty_init(c, n) and the other conf.h initialisers, which paste the driver’s prefix onto each entry point (dev_init(c,n,open) → comopen) and pick enxio/enodev/nullop by count const fn cdev_tty_init(c, open, close, read, write, ioctl, stop, tty, kqfilter) -> Cdevsw taking the driver’s functions in the macro’s order; the casts of enodev & co. are small functions of each pointer type Rust has no token pasting; a const fn keeps each macro’s choices (which slot becomes enxio at count 0) and builds the table at compile time
comparing function pointers to find a driver’s major (cdevsw[maj].d_open == comopen, ptyioctl’s == ptcopen) ptr::fn_addr_eq(cdevsw(maj).d_open, comopen as DevTypeOpen) == on fn pointers is linted as unreliable; fn_addr_eq states that the address is what is compared, as in C
a structure member shared through a pointer and its field-alias macros (struct tty’s t_termios with #define t_lflag t_termios.c_lflag, t_cc, t_ospeed) t_termios: Cell<Termios> with accessors named after the macros (tp.t_lflag(), tp.set_t_lflag(v), tp.t_lflag_set(ECHO), tp.t_cc(VERASE)), and Cells for every other member that changes the driver’s interrupt, its soft interrupt, the clock and the reading process all change the tty through struct tty * at spltty(); Cell keeps &Tty shared, and the accessors keep the C’s spelling
pointer cursors into a ring buffer (struct clist’s c_cf, c_cl, c_ce, the u_char * that firstc/nextc hand out) indices into the ring, the ring and its quote bitmap reached as &[Cell<u8>] slices over the clalloc allocation; firstc/nextc return Option<usize> every access is bounds-checked and no raw pointer arithmetic survives; the slices are Cells because the queue is changed through &Clist
*(int *)data, *(struct termios *)data = tp->t_termios in a device or line-discipline ioctl whose data is the byte slice sys_ioctl copied let v: i32 = ioctl_arg(data), ioctl_ret(data, &termios) (sys/ioctl.rs), over AbiPod types the command encodes the size sys_ioctl copied, so the read and write are plain unaligned copies of an AbiPod value; no unsafe in the handlers
a member of a type from an unported subsystem that stays empty until it is ported (struct selinfo’s si_note, a struct klist, until kern_event.c) the member is left out, the function that only walks it (selwakeup) is a documented no-op, and every function that would add to it (ttkqfilter, ptckqfilter) reports itself nothing can insert into the list, so nothing is lost; the gap is visible at the only places that would fill it
an on-disk structure the C changes in place through a shared pointer (struct fs in core, struct cg in a buffer: fs->fs_cstotal.cs_nbfree--) #[repr(C)] with every member a Cell (Cell<T> has T’s layout), offsets pinned by const asserts, reached as &Fs over the bytes (malloced, or a busy buffer’s mapping); counters through add/sub the layout is the disk’s; any bytes are a valid value of Cells of integers, so the one unsafe is where the bytes become the reference
a header plus arrays at offsets it records, all in one buffer (cg_blksfree(cgp), cg_inosused(cgp), the struct ocg fallback) a view (CgBuf) over the busy buffer: cg(&self) -> &Cg for the header, &mut self methods returning the maps as byte slices past it the borrow checker keeps a map slice from coexisting with another one, and the slices never cover the header the Cells are in
DIP(ip, field) / DIP_ASSIGN(ip, field, v): one member of the inode’s ufs1_dinode or ufs2_dinode, picked by um_fstype ip.dip_field() / ip.dip_set_field(v), generated by a macro_rules! per member; the getter returns the wider type (the C conditional’s), the setter truncates (the C assignment’s); the dinode stays a pool item behind a raw pointer, borrowed only inside the accessor the C’s member-name pasting needs a macro; keeping the borrow inside the method means no reference to the dinode outlives one access
a static int done once-only flag in an *_init (ffs_init, ufs_init) an AtomicBool with swap(true), which the host tests clear before vfsinit the tests give every run fresh memory, so the pools and tables the flag guards must be set up again
a function returning 0 (“go on”) or -1 (“done”: an error already recorded in the buffer, or end of partition) that is not an errno (bounds_check_with_label) bool: true for the C’s 0 the outcome is a branch, not an error; Err(ERESTART) would misread -1
a driver structure the caller fills through a pointer while a callback reads it (sdgetdisklabel(dev, sc, sc->sc_dk.dk_label, ...) while the strategy bounds-checks against dk_label) fill a local, publish the fields the callback needs before the call, install the result after (rdgetdisklabel, rdopen) a &mut may not alias what another path reads during the call; the fields the callback needs do not change while the label is read
a struct mbuf **mp walked down a chain the function builds (*mp = m; mp = &m->m_next; ... *mp = NULL, soreceive’s mp0/controlp) a private cursor holding the caller’s &mut Option<&'static Mbuf> and the last mbuf appended (MbufTail: get, set, advance); a local chain head works the same way with an Option<&'static Mbuf> link (somove) a pointer into either the caller’s slot or an mbuf’s m_next has no safe Rust spelling; the two cases are an enum in effect
comparing two function pointers (sosp->so_proto->pr_usrreqs->pru_send != so->so_proto->pr_usrreqs->pru_send) core::ptr::fn_addr_eq(a, b) the explicit form of the C comparison; plain == on fn pointers is linted as unreliable
explicit_bzero(&ctx, sizeof(ctx)) over a context struct (chacha_ctx, poly1305_state, blake2s_state, a fe) crate::crypto::wipe(&mut ctx): assign T::default() through the &mut, then black_box it; byte buffers keep libkern::explicit_bzero the C needs an opaque call so the store is not elided; the assignment is observable by the caller and black_box keeps the optimiser from proving it dead, with no unsafe and no byte view of a struct that may have padding
a crypto primitive whose output may be the input buffer (chacha_encrypt_bytes(x, m, m, n), chacha20poly1305_encrypt(buf, buf, ...), blake2s_hmac(out, out, ...)) the separate-buffer call takes &[u8] and &mut [u8]; the aliasing calls are a second function, *_inplace(buf: &mut [u8], ...), over one buffer a & and a &mut of the same bytes cannot coexist; each C call site is one of the two shapes, so each keeps the C name’s meaning with no copy where the C had none
a primitive’s static helper header included by several .c files with a compile-time switch (chacha_private.h with and without KEYSTREAM_ONLY) one module with a function per variant (chacha_encrypt_bytes, chacha_keystream_bytes) a cfg feature would make one build unable to hold both users; the two variants share the block function
machine-generated straight-line arithmetic (fiat-crypto’s fe_mul_impl, one variable per partial product) loops that form the same sums and the same carry chain, with the equivalence stated in the module doc and pinned by the RFC vectors the 800 unrolled lines are not readable Rust; the limbs it returns are identical, so the vectors (RFC 7748 iterated 1000 times) check the transcription
a lookup table that is a function of a mathematical definition (rijndael.c’s Te0..Td4 and the S-box, SHA-2’s round constants) tables computed by const fn into a static (rotated variants from the first), or the constants written once; a #[ignore]d reference-backed test compares each with the C file’s table (testutil::c_table) a 1 KiB table typed in by hand is a place for a typo; the generator is the definition and the C table is the oracle
a licence block whose first line is /** (a doc comment opener in Rust) /* *, the text of every line unchanged /** makes rustc read the block as an outer doc comment of the next item; the delimiter is not licence text
union authctx and the void *ctx of struct auth_hash; the key schedule caddr_t of struct enc_xform (ctxsize bytes malloced by the caller, initialised in place by Init/setkey) an enum (AuthCtx, Kschedule) whose variant Init/setkey creates, Boxed by the caller and matched by each function; the wrong variant is a panic (a table wired to the wrong functions) the C’s one block of bytes has several types and only the table knows which; an enum is the same fact with the compiler checking each function against it, and ctxsize stays as the sizeof of the Rust types
a request or descriptor taken from a pool and handed through callbacks (crypto_getreq, struct cryptop with crp_desc pointing into itself or a second allocation) a value returned by crypto_getreq (Option, None on failure) owning a Vec of descriptors, passed as &mut Cryptop; no pool the request lives for one synchronous call (crypto_invoke), so ownership is plain; the NULL of pool_get(PR_NOWAIT) is try_reserve failing
a linked list of configuration structures built on the stack (crie.cri_next = &cria) Option<&Cryptoini> links, built last element first; a descriptor array chained in place (crypto_invoke’s migrate) is rebuilt by recursion over split_last a shared reference to a sibling cannot be set after the fact without Cell; building from the tail keeps every link a borrow the compiler checks
a global table the C touches under the kernel lock and splvm (crypto_drivers, swcr_sessions) a StaticCell<Vec<T>> reached only through a closure (with_drivers(\|d\| ...)) that never calls a driver or itself the one unsafe is in the accessor; a closure cannot leak the &mut, and the code that calls a driver callback copies the fn pointer out first
struct klistops callbacks over an opaque void * that the registration pairs with them (klist_init(kl, ops, arg), klist_init_mutex(kl, &mtx)) the callbacks are unsafe fn(*const c_void); klist_init and its wrappers are unsafe fns whose contract is that arg is what the ops expect and outlives the list the list never looks inside the pointer; the one call that pairs it with its ops is the place that can vouch for it, as an object’s own klistops (the socket layer’s) will
a structure on the caller’s stack whose members are linked into a shared queue between two calls (struct kqueue_scan_state’s marker knotes, from kqueue_scan_setup to kqueue_scan_finish) the setup call is an unsafe fn whose contract is that the state stays in place, and is not dropped, until the finishing call the borrow checker cannot see a link that outlives the call that made it; the futex idiom covers one call, this one spans the caller’s loop, so the caller states it once
a const struct filterops compared by address (kn->kn_fop == &badfd_filtops, == &user_filtops) a static of the ops struct, Cell<Option<&'static Filterops>> in the knote, ptr::eq(kn.fop(), &BADFD_FILTOPS) a static has one address, as the C’s const object does; a const item would not
a marker structure that shares a list element’s first members and sits in its list (struct inpcb_iterator, cast to struct inpcb * for TAILQ_INSERT_AFTER) a whole element whose identifying member is None (InpcbIterator(Inpcb), inp_table and inp_socket None), and the walk an unsafe fn whose contract keeps the marker in place until it is removed the list adapters hand out &Elem; a shorter marker read as an element would be undefined behaviour, a whole one is just an element the walks skip
an ABI structure that is also a kernel object, with kernel pointers and members pf changes after publication (struct pf_rule in struct pfioc_rule and in a ruleset; struct pfi_kif, struct pf_src_node, struct pf_queuespec) one #[repr(C)] type with the C’s layout (holes as named _pad members, a layout test against clang’s offsets), changing members as Cells, pointer members as raw Cell<*const T> words read through accessors that take &'static self; moved across the boundary by pf_abi_read/pf_abi_write/pf_abi_copyin/pf_abi_copyout under the unsafe trait PfAbi (the AbiPod promise for types with Cells) only kernel objects are 'static, and they hold valid pointers (built by the copyin functions that copy scalars only); a user copy lives in a Box or a local and cannot reach the accessors
a packet descriptor’s pointers into the packet or into its own header copy (pd->src = &h->ip_src, pd->sport = &pd->hdr.tcp.th_sport, pd2.src = &h2.ip_src) and the u_int16_t */struct pf_addr * arguments that may be any of them PfLoc: Hdr(offset) into the descriptor’s hdr, Mbuf(offset) into the first mbuf’s data, or Ptr to a header copy on the caller’s stack (made unsafely); reads and writes go through the descriptor (ld16, st16, ld_addr, st_addr, unaligned) a pointer into the descriptor itself would be invalidated by the next &mut to it; offsets are resolved at each use and the bounds are checked
a pool item handed out zeroed and filled through its pointer (pool_get(&pf_state_pl, PR_NOWAIT \| PR_ZERO)) pf_pool_get::<T>(&POOL, flags) -> Option<&'static T> over unsafe trait PfPoolItem (all-zero is a valid T), pf_pool_put(&POOL, obj) the zeroing is what makes the reference valid; the trait states it once per type
a caller-embedded pair of tree nodes linked by raw pointers (struct radix_node pfrke_node[2] inside a table entry, handed to rn_addroute) the nodes are Cell<*const RadixNode> links, the tree functions unsafe fns whose contract is the tree invariant stated in net/radix.rs; the entry embeds the pair first (#[repr(C)]) so the returned leaf is cast back to it the radix code follows links into memory it does not own; the one contract covers every walk
pf’s global heads, trees and tables (pf_statetbl, tree_id, pf_anchors, pfi_ifs) static NAME: PfGlobal<RbtHead<..>>, a Sync newtype that derefs to the head the C changes them under the net lock and pf’s locks; the wrapper states that once instead of a newtype per head
a message parsed into an array of pointers to its parts, read and written through them by the handlers (PF_KEY’s void *headers[SADB_EXT_MAX + 1] into the sadb_msg buffer) the buffer is a Vec<u8> owned by the handler, the parts SadbHeaders = [*mut u8; N] into it, every access read_unaligned/write_unaligned (sadb_get/sadb_put) inside unsafe fns whose contract is that the pointers come from pfkeyv2_parsemessage over a live buffer the extensions are 8-byte aligned in the message but not in a Vec<u8>; the handlers keep the C’s shape (several parts read and patched in place) without a borrow per part
a negative errno as an in-band verdict beside the positive ones (ipsp_spd_lookup’s -EINVAL: drop silently, not an error to report) a small error enum (SpdError::Drop, SpdError::Errno(e)) with From<Errno> Errno has no negative values except ERESTART/EJUSTRETURN; the callers match the two cases where the C compared the sign
a union compared and hashed as bytes (union sockaddr_union, struct sockaddr_encap as a radix key with bcmp/memcmp over its length) a #[repr(C)] byte image of the union’s size with accessors for each view at the C offsets (SockaddrUnion, SockaddrEncap) every byte is always initialised, so the C’s byte comparisons are slice comparisons and the radix key is the image itself
a function that may free the object it is given and returns it or NULL, the caller overwriting its pointer (tp = tcp_close(tp), tcp_drop, tcp_usrclosed, tcp_dodisconnect) fn(tp: &'static T) -> Option<&'static T>, the caller rebinding (let tp = tcp_usrclosed(tp)) and using only the result the C’s idiom is exactly a rebinding; shadowing the name leaves no live path to the freed object
a pointer argument kept only as an address, possibly to an object already freed (tcp_trace’s otp, recorded in td_tcb after the request may have closed the connection) *const T taken and stored as a usize, never dereferenced a reference would claim a live object; the address is all the C reads
a stream structure of buffer cursors (zlib’s z_stream: next_in/avail_in, next_out/avail_out advanced by the library) ZStream<'a> with next_in: &'a [u8] and next_out: &'a mut [u8], re-sliced as the C advances the pointers (take_in, put_out); avail_* are the lengths a pointer and its count can never disagree, and the borrow checker keeps the caller’s buffers alive for the stream’s lifetime
build options that pick code with #ifdef (-DSLOW -DSMALL -DNO_GZIP in sys/lib/libz/Makefile) pub const SLOW: bool = true and if SLOW { .. } else { .. } at each site; a cfg feature only when the other side cannot compile both sides keep compiling and can be tested (the inflate tests run each stream with and without inflate_fast)
an allocator hook that may return NULL (ZALLOC(strm, n, size), mallocarray(.., M_NOWAIT)) zcalloc::<T>(n) -> Option<Vec<T>> (try_reserve_exact, zero-filled), zcalloc_box for one state the C’s Z_MEM_ERROR path survives; Rust’s infallible vec! would abort the kernel instead
a resumable state machine with goto inf_leave and register-cached locals (inflate()’s LOAD()/RESTORE() and NEEDBITS/DROPBITS macros) a Regs value with methods for the macros, loop { match state.mode { .. } } and a labelled break 'inf_leave the macros’ hidden locals become one borrowable value; every exit still writes the registers back
a function returning a length and the buffer through u_int8_t **out, 0 meaning failure (deflate_global, comp_algo.compress) Result<Vec<u8>, Errno>: the vector’s length is the C’s return value the buffer is freed by its owner on every path, and the failure is not a length
function-pointer call-backs with a void * descriptor (inflateBack(strm, in, in_desc, out, out_desc)) closures (impl FnMut() -> &'a [u8], impl FnMut(&[u8]) -> i32) the descriptor is what the closure captures
a value returned with failure flagged through an int *err out-parameter (bpf_ops’ ldw/ldh/ldb, bpf_mbuf_ldw) -> Option<T>, None where the C sets *err = 1 the flag and the value cannot disagree, and ? replaces if (err != 0) return 0
int f(...) that always returns an errno, never 0 (pledge_fail’s return (error), or ENOSYS under “error”) fn f(...) -> Errno; callers write return Err(pledge_fail(p, Errno::EPERM, code)) the type says it cannot succeed, so no caller tests a success that never comes
#ifdef CPU_CHR2BLK (a <machine/cpu.h> name some machines define) tested in machine-independent code (pledge_sysctl) an associated const NAME: Option<i32> = None of the machine trait, overridden with Some(..) by the machines that define it, read as crate::machine::cpu::NAME generic code cannot name an arch module; None is the C’s “not defined” and the default keeps the other machines untouched
a statement macro whose arguments are output lvalues filled by an instruction (CPUID(code, eax, ebx, ecx, edx), CPUID_LEAF) a function returning the outputs as a tuple, destructured at the call: let (eax, ebx, _, edx) = cpuid(code); Rust has no output macro arguments; the tuple keeps the C’s register order and names the unused ones _
wire structures made of u_int8_t fields and arrays, cast from a CDB or a reply buffer ((struct scsi_inquiry *)&xs->cmd, (struct scsi_direct_blk_desc *)&buf->buf[offset], union scsi_mode_sense_buf) #[repr(C)] structs of u8s with their size pinned by const asserts, marked with unsafe trait ScsiWire (alignment 1, no padding, any bit pattern valid), which gives zeroed, as_bytes, as_bytes_mut, read_from; wire_ref::<T>(bytes)/wire_mut::<T>(bytes) are the casts (bounds-checked); a union of such structs is the byte array with typed views the cast is sound exactly because every byte layout is a valid value; the trait states that once per type and the views check the length the C does not
xs->data/xs->datalen: a buffer pointer a transfer carries from the issuer to the adapter (a local, a softc member, a buf’s b_data) private raw pointer and length in ScsiXfer, written only by unsafe fn set_data(ptr, len) (contract: valid and reserved for the transfer until it completes), read as data()/datalen() (raw, for DMA loads) or unsafe fn data_slice() the buffer’s owner is a run-time fact; putting the one unsafe where it is chosen lets scsi_copy_internal_data and the adapters’ copies be safe, as the C trusts the issuer once
a struct cast from its first member found on a shared queue, the kind told by comparing a function pointer ((struct scsi_xshandler *)ioh when ioh->handler == scsi_xs_get_done) a private kind flag in the member (ScsiIohandler.is_xsh), set by the constructor of the outer struct, checked before the cast; the handler comparisons stay ptr::fn_addr_eq Rust may give two functions with identical bodies one address (scsi_io_get_done, scsi_xs_get_done), so the address cannot be the type tag the cast relies on
dma_alloc(len, PR_WAITOK \| PR_ZERO) … dma_free(p, len) around a command’s data buffer (scsiconf.c, scsi_ioctl.c), while kern/dma_alloc.c is not ported DmaBuf::new(len, M_WAITOK) (scsi/scsiconf.rs, pub(crate)): zeroed malloc(9) memory (M_DEVBUF), bytes()/wire::<T>() views, freed on drop; a transfer’s data is cleared and the transfer put back before the buffer drops both archs’ bus_dma maps any kernel memory; the drop keeps every early return from leaking it, and the order keeps the adapter from writing freed memory
a matcher over a caller’s table given as base pointer, count and element size (scsi_inqmatch(inq, base, nmatches, matchsize, &pri)), whose elements are or begin with the pattern fn scsi_inqmatch<'a, T: AsRef<ScsiInquiryPattern>>(inq, base: &'a [T]) -> (Option<&'a T>, i32) the slice carries count and stride; AsRef names the embedded pattern the C reaches by casting; the out-parameter becomes the second return value
a C global whose name differs from a macro’s only by case (int scsi_autoconf = SCSI_AUTOCONF;) #[allow(non_upper_case_globals)] pub static scsi_autoconf: AtomicI32 beside pub const SCSI_AUTOCONF the upper-case Rust name is taken by the constant; the C name keeps grep working (as cryptosoft.rs does)
a device switch entry point called internally with struct proc * NULL (sdsize calling sdopen(dev, 0, S_IFBLK, NULL)) while d_open takes a &Proc the entry point is a thin wrapper over <name>_noproc(dev, flag, fmt), which the internal caller uses; d_close already takes Option<&Proc> the driver never reads the thread, and a fake Proc would be worse than a second name
goto foundentry / goto notfound from a fast path before a loop into the middle of the loop’s body (ufs_lookup’s UFS_DIRHASH path) the fast path computes what the label’s code would and returns a flag (hashed); the loop runs only when the flag is clear Rust has no goto into a loop; the jump’s targets only set results the code after the loop reads, so skipping the loop is the same control flow
a two-level table of pool blocks reached through a malloced array of pointers, indexed by a macro (DH_ENTRY(dh, slot) over doff_t **dh_hash) the pointers stay raw (Cell<*mut *mut Doff> beside dh_narrays/dh_hlen); a safe accessor pair (dh_entry/set_dh_entry) checks NULL and the index and panics, and the arrays are freed only after the pointer is cleared the table can be stolen (recycled) under its lock while its owner keeps the structure, which no lifetime expresses; the one unsafe is in the accessor, and an out-of-range slot is a panic instead of the C’s stray read
an on-disk structure made only of u_int8_t/int8_t members and arrays, cast from a buffer ((struct direntry *)(bp->b_data + off), struct byte_bpb710, struct winentry, union bootsector) #[repr(C)] structure of u8s and [u8; N]s with byte_view! (sys/msdosfs/bpb.rs): T::at(buf, off) -> &T, T::at_mut(buf, off) -> &mut T, T::SIZE, and a compile-time check that the alignment is 1; multi-byte members read with getushort/getulong (little-endian); a u_int16_t member that the kernel only zeroes becomes [u8; 2]; cd9660’s ISO 9660 descriptors and Rock Ridge entries the same way, with from_bytes(&[u8]) -> Option<&T> (the C’s length < sizeof(*p) test) and, for a variable-length record (struct iso_directory_record), a view type over the bytes to the end of the buffer with one accessor per field with alignment 1 and no invalid bit patterns, a reference over the bytes is sound at any offset, so the C’s cast survives as a safe call; the borrow of the buffer’s slice keeps the view from outliving it
a function-like macro applied to arguments of several integer types, computing in the argument’s type (de_cluster(pmp, off) on an off_t and on a uint32_t, cntobn, de_clcount) a generic fn de_cluster<T: DeArith>(pmp, off: T) -> T over a small trait implemented for u32, u64, i64 (from_u32, wrapping wadd/wsub; shifts from core::ops) each call keeps the C’s width and wrap-around, which one fixed Rust type would change for either the 32-bit cluster arithmetic or the 64-bit offsets
a group of C globals that remember the last computation as a cache (lasttime, lastday, lastddate, lastdtime in unix2dostime) one AtomicU64 holding the whole entry (key and result packed), 0 for “empty”; a member derivable from the others (lastday) is not stored a reader can never pair the key of one computation with the result of another, even with the host tests running in parallel, and no lock is needed for a pure cache
an on-disk __packed structure of little-endian integer members (and nested packed structures, flexible x[1] tails) cast from a buffer (ecma167-udf.h: (struct file_entry *)bp->b_data, struct long_ad * at &fentry->data[l_ea]) #[repr(C, packed)] with the C’s integer members, marked with the unsafe trait Packed (sys/isofs/udf/ecma167_udf.rs): T::at(buf, off) -> Option<&T>, T::SIZE, alignment 1 asserted at compile time; integer members read by value (letoh32(fe.l_ea), braces in format_args!), nested structures by reference; a flexible x[1] member is x: [T; 0] and the tail is read from the buffer past SIZE alignment 1 and no invalid bit patterns make the in-place view sound at any offset; at checks the bounds the C’s cast does not; a reference to an integer member of a packed structure is undefined behaviour, a by-value read is not
a C file name with a - (isofs/udf/ecma167-udf.h) the same name with _ (ecma167_udf.rs), noted in the file’s deviations and in its ports.toml row a - cannot appear in a Rust module name; #[path] would keep the name but hide the module from use paths that grep for it
__packed on-disk structures read and changed in place inside I/O buffers through shared pointers (struct sr_metadata, sr_meta_chunk at stride 92, sr_meta_opt_hdr, sr_meta_crypto) #[repr(C)] structures of alignment-1 cells: Cell<[u8; N]> for byte arrays, PackedCell<T> (a Cell<[u8; size_of::<T>()]> read and written in machine byte order) for integers; unsafe trait SrMetaView gives view(&[Cell<u8>]) -> Option<&T>, cells(), copy_from, bzero; buffers are &[Cell<u8>] and sr_view::<T>(area, off) is the C’s pointer arithmetic a reference to a misaligned u64 is undefined behaviour and Cell<u64> inside a packed struct cannot be borrowed; alignment-1 cells have the C’s exact layout (asserted), any bytes are a valid value, and shared views may be written as the C does
a kernel structure the C allocates with malloc(..., M_ZERO) and uses at once (struct sr_discipline, sr_workunit, sr_chunk) unsafe trait SrZeroed (every member valid as zero bytes, no Drop) and sr_malloc::<T>(flags) -> Option<NonNull<T>> / sr_malloc_size::<T>(size, flags), sr_free; a type with a member that is not zero-valid (a ccb’s struct buf) is written in place with its const fn new() no multi-kilobyte value is built on the kernel stack to be moved into the allocation, and the marker states the one fact that makes the zeroed bytes a value
a table of function pointers inside a structure that callers invoke as sd->sd_scsi_rw(wu) and test for NULL as if (sd->sd_scsi_sync) pub sd_scsi_rw: Cell<Option<SdScsiFn>> plus a method of the same name that calls it (sd.sd_scsi_rw(wu)), panicking with the hook’s name where the C would call through NULL; NULL tests stay sd.sd_scsi_sync.get().is_some() fields and methods live in different namespaces, so the call reads like the C and the installation (sd.sd_scsi_rw.set(Some(f))) too
a structure whose allocation size a member records so that a caller can cast to a larger type that embeds it first (sd_wu_size = sizeof(struct sr_crypto_wu), (struct sr_crypto_wu *)wu) unsafe trait SrWorkunitExt (#[repr(C)], the base first, zero-valid), sd.set_wu_type::<T>() records the size and TypeId::of::<T> (as an fn() -> TypeId, zero-valid), sr_wu_ext::<T>(wu) checks both before the cast the size alone does not name a type; the recorded TypeId makes the cast safe to call
an ioctl argument structure with padding (struct bioc_vol, bioc_disk, … behind struct bio) cast from the ioctl data and changed in place ((struct bioc_vol *)bio) a private unsafe trait BioArg (every bit pattern valid) and bio_arg::<T>(addr), a read_unaligned copy out of the ioctl’s bytes; the members the C changes are written back one by one at their offset_of! (bio_put), the struct bio_status member by member writing a whole padded structure back would put uninitialised padding bytes into the copied-out buffer; the bytes are the ioctl’s, so nothing else needs to be AbiPod
a driver function that decides which I/Os to queue and how their data combine (sr_raid5_regenerate, sr_raid5_write, sr_raid6_rw’s strip body: which chunks to read, which buffers to XOR into, with which GF(256) factor) and that calls only a handful of primitives (sr_*_addio, sr_block_get/put, memset, the XOR routines, the chunk status) the function is generic over a small private trait of those primitives (Raid5Io, Raid6Io, associated types for the work unit and the buffer); the kernel’s implementation wraps the real ccbs, the host tests’ runs the queued I/O on in-memory chunks the decision logic (four RAID 5 write cases, the RAID 6 recovery factors) is the part worth testing and needs real bytes; a trait keeps one copy of it instead of a test-only re-implementation, and costs nothing at run time (monomorphised)
a table the C fills at run time from a formula in an init function called before every use (gf_init filling gf_pow[]/gf_log[] in each sr_raid6_discipline_init) the init function becomes the const fn that builds a read-only static (static GF: GfTables = gf_init();); per-key tables the C allocates lazily (gf_premul’s gf_map[gn]) stay lazy, as [AtomicPtr<u8>; 256] installed by compare-and-swap the values never change after init, so a compile-time table removes the mutable global and the init-before-use ordering; the lazy tables keep the C’s memory behaviour and its M_NOWAIT failure path
a struct cryptop * preallocated per work unit (crypto_getreq(MAXPHYS >> DEV_BSHIFT) at setup) and filled for each I/O with pointers to that I/O’s buffer and key (softraid_crypto.c’s cr_crp) the work unit keeps only the descriptors’ allocation, as the parts (pointer, capacity) of an empty Vec<Cryptodesc> (SrCryptoWuReq::crp_take/crp_put); each I/O rebuilds the vector, builds a Cryptop<'a> over a local Uio and a copy of the key, and empties the vector again before giving it back a Rust Cryptop<'a> borrows its buffer and key, so it cannot outlive one I/O; an empty vector holds no value of the borrow’s lifetime, so its allocation can be kept across I/Os (one unsafe in crp_take) and nothing is allocated at I/O time, as the C promises
a function that returns an object set up for the caller’s next call (crwu = sr_crypto_prepare(wu, mdd, 1); rv = crypto_invoke(crwu->cr_crp);) when the object borrows locals of the function the function takes the call as a closure (sr_crypto_prepare(wu, mdd, true, crypto_invoke)) and returns its result with the object the borrowed buffer and key copy live exactly as long as the call needs them
a struct malloced with its variable-length payload right behind it (struct netcred plus the address and mask, np + 1) #[repr(C)] struct, malloc(size_of::<T>() + payload), the payload reached with ptr::add(size_of::<T>()) from the allocation pointer; the first member is the radix node pair so the leaf rn_match returns casts back to the struct; the allocation is padded where a reader (the radix code reads max_keylen bytes) may go past the length byte the C’s layout trick kept, with the one over-read the C leaves to the malloc bucket made explicit (vfs_hang_addrlist)
tpr_t (a struct session *, NULL for “no terminal”) Tpr = Option<&'static Session> (sys/sys/tprintf.rs) NULL is None; tprintf_open takes the reference (SESSHOLD), tprintf_close drops it
a caddr_t dpos cursor into mbuf data and the u_int32_t *tl that nfsm_dissect/nfsm_build return into the chain (*tl++ = txdr_unsigned(x), fxdr_unsigned(int, *tl)) the cursor stays (Option<&'static Mbuf>, *mut u8) but only the primitives dereference it, after nfsm_avail checks it lies in the mbuf’s data; the pointers become bounds-checked views that borrow the cursor: XdrIn (get(i) raw word, fxdr_hyper, bytes, read::<T: AbiPod>) and XdrOut (set(i, w), put(w) for *tl++, txdr_hyper, write::<T>) a view cannot outlive the reply it reads (the chain cannot be freed or dissected further while it is borrowed), every word access is checked, and the one unsafe per primitive is where the view is made
int *errorp / nmi_errorp filled by the nfsm_* helpers, then goto nfsmout where the reply is freed each helper returns Result and, on failure, frees and clears the chain itself (nmi_mrep, nmi_mreq, nd_mrep) as the C does before storing the error; the caller writes the body before nfsmout: as an inner closure with ? and keeps the nfsmout: tail after it the error and the cleanup travel together, and m_freem(info.nmi_mrep.take()) at the tail is a no-op when a helper already freed the chain
globals computed once at boot from constants (rpc_call = txdr_unsigned(RPC_CALL) in nfs_init) pub const rpc_call: u32 = txdr_unsigned(RPC_CALL) with the C’s lowercase name (#[allow(non_upper_case_globals)]) txdr_unsigned is a const fn; the uppercase name is already the header’s constant
an int * flag word passed to a lock routine that sleeps on its address and that members of different structures serve (nfs_sndlock(&nmp->nm_flag, rep), nfs_sndlock(&slp->ns_solock, NULL)) &Cell<i32>, the sleep/wakeup channel ptr::from_ref(flagp) the member is already a Cell in its &'static structure; the reference keeps both the bits and the address the sleepers wait on
an NFS server function whose int error is an errno or an NFS status that goes into nd_repstat (nfs_namei, nfsrv_fhtovp: NFSERR_AUTHERR \| AUTH_TOOWEAK) Result<T, i32>; an Errno converts with Errno::as_i32(); the NFS status values stay i32 (NFSERR_*) Errno names only errno numbers, and the status space is wider (10001.., the NFSERR_RETVOID/AUTHERR/RETERR flag bits); the caller stores the value in nd_repstat as the C does and nfsrv_errmap maps it
a (int ret, struct vattr *vap) pair where a non-zero ret means “VOP_GETATTR failed, vap is garbage” (nfsm_srvwcc(nfsd, before_ret, before_vap, after_ret, after_vap, mb)) Option<&Vattr>, None for the failed fetch no uninitialised structure travels with a flag; the C’s flag is the type
a function that fills the wire structure the caller has just nfsm_buildt (nfsm_srvfattr(nfsd, vap, fp)) returns the structure by value (NfsFattr); the caller writes it with XdrOut::write(0, &fp) (a version 2 reply holds the first 68 bytes) the structure is built on the stack, then copied into the view that bounds-checks it
a file-scope LIST_HEAD(, T) *hashtbl allocated by hashinit() with u_long mask, a SIPHASH_KEY and a TAILQ_HEAD of the same items (nfs_srvcache.c, ufs_ihash.c) StaticCell<Table(&'static [ListHead<A>])> (a Sync wrapper under the module’s SAFETY note), the mask and key in StaticCells written once by the init function, the LRU list in a Sync wrapper over TailqHead; counters are atomics the same shape as ufs_ihash.rs; nothing mutable is a static mut, and the list’s lock is the kernel lock as in C
an NFS client RPC whose many exits goto nfsmout with *nmi_errorp set and whose tail must run with the variables the body changed (newvp to vput, wccflag, cn_flags) a labeled block let error = 'nfsmout: { ... }; whose failures leave through nfsm_try!('nfsmout, expr) (match expr { Ok(v) => v, Err(e) => break 'nfsmout Err(e) }), then the C’s tail a closure (CONTRACT.md’s pattern) cannot return from the operation nor mutate the locals the tail reads while borrowed; macro_rules! labels are hygienic, so the label is passed in
#if defined(NFSCLIENT) \|\| defined(NFSSERVER) around a syscalls.master entry GATED in tools/xtask/src/syscalls.rs: the generator configures the option and emits two cfg-selected rows of sysent[] (the function; the #else branch’s sys_nosys) plus a cfg-ed import; the function is found in sys/nfs/ too the table keeps its length with the feature off; no sys_* name has to exist without it
a union mount_info member copied with bcopy(argp, &mp->mnt_stat.mount_info.nfs_args, sizeof(*argp)) nfsargs_bytes(&NfsArgs) writes the fields one by one into a zeroed [u8; SIZE] (padding zero), NfsArgs::from_bytes reads it back; nfs_decode_args works on a copy the structure has padding holes; copying it as a typed value would leave them indeterminate in what statfs(2) copies out
pool_walk(pp, full, db_printf, func) with void (*func)(void *, int, int (*)(const char *, ...)) pool_walk(pp, full, pr: fn(Arguments) -> usize, func: fn(*const u8, bool, PoolWalkPr)) db_printf is fn(Arguments) -> usize here
a helper whose non-zero return tells the caller to return 0 because it has already built the final reply (if (nfsm_reply(nfsd, slp, mrq, &mb, error, len) != 0) return 0;), with the build cursor in an out parameter Result<Option<&'static Mbuf>, Errno>: Some(mb) = go on building at mb, None = done (let Some(mut mb) = nfsm_reply(..)? else { return Ok(()) };), Err = the reply could not be started the cursor only exists when the caller may use it; the three outcomes are distinct types instead of a flag plus an out parameter
a tl/fp pointer kept from an earlier nfsm_build and written after more of the reply (and its data) has been appended (nfsrv_read’s attributes and count) remember the mbuf and the offset of the area (m_len - len right after the build) and write it later through nfsm_rebuild(m, off, len), a bounds-checked XdrOut the view of nfsm_build borrows the build cursor, which the code keeps using; an offset into an mbuf of the chain stays valid and is checked when used
a void *rdata / struct uio *uio parameter pair, one of them NULL in every call (ntfs_readattr, ntfs_readntvattr_plain) enum Rdata { Mem(&mut [u8]), Uio(&mut Uio) }, plus the offset where the C’s advanced data pointer stands the two are alternatives; the enum makes “exactly one” the type’s, and copies into Mem are bounded by the slice
a pointer to a little-endian on-disk record inside a buffer (ep = (struct ext2fs_direct *)(bp->b_data + off), letoh16(ep->e2d_reclen), an overlapping memcpy(ep, nep, n)) an offset into the buffer’s bytes with free accessors that convert (e2d_reclen(b, off), set_e2d_reclen(b, off, v)), copy_within for the overlapping copy, write_to/to_le_bytes for whole structs the record is unaligned and little-endian on every host; reading it in place keeps the C’s one-buffer algorithm (compaction, rec_len chains) with no unsafe cast
kernel code that sleeps until a userland daemon answers through a device (fb_queue’s tsleep on the fusebuf until fusewrite wakes it) the C’s code; in cfg(test) builds only, a thread-local hook (TEST_DAEMON) runs between queueing and sleeping and plays the daemon through the device’s real read and write entry points the host has no context switch and sleeps return at once while cold; the hook drives the whole protocol in host tests without changing the kernel build
struct __mp_lock and kernel_lock (KERNEL_LOCK(), __mp_release_all) under MULTIPROCESSOR MpLock with atomic tickets in sys/sys/mplock.rs, static KERNEL_LOCK in kern/kern_lock.rs; KERNEL_LOCK() is sys::systm::kernel_lock(), an empty #[inline] function without feature multiprocessor the macros are nothing on a uniprocessor kernel; a function keeps every call site free of cfg
a volatile per-CPU counter, or a field another CPU reads without the writer’s lock (spc_nrun, spc_curpriority, spc_cp_time, ci_want_resched) AtomicU32/AtomicU8/AtomicU64/AtomicI32 with Relaxed; a single writer does store(load() + n), not fetch_add relaxed accesses compile to the C’s plain loads and stores, without a data race in Rust’s model
a struct cpu_info pointer field another CPU reads (ci_curproc, ci_proc_pmap) CiPtr<T> / AtomicPtr<T> with relaxed get/set, #[repr(transparent)] so the assembly’s offsets still hold Cell<*const T> is only sound for fields one CPU touches
an object on the caller’s stack handed to an API that keeps it while the caller sleeps until it is done (sched_barrier’s task, smr_barrier’s entry) an unsafe fn ..._local(&T) variant, or a &'static lent from &*ptr::from_ref(&x) with a SAFETY note naming the wait the safe API stays 'static for the usual case
#ifdef MULTIPROCESSOR inside a .S file pulled in with global_asm! (amd64 vector.S) .if {MULTIPROCESSOR} with the operands passed by a macro_rules! wrapper: 1 and sym operands with the feature, const 0 for every one without one .S stays diffable against the C; asm! operands cannot be cfg‘d one by one
km_alloc(sizeof *cif, ..., &kp_zero, ...) for a large, mostly plain structure (cpu_info_full) km_alloc with KP_ZERO, then addr_of_mut!((*p).field).write(Field::new()) only for the parts that need a constructor no structure-sized temporary on a kernel stack
a command block’s void * cookie that its fill and done callbacks cast to what the issuer set (ccb_cookie of nvme(4): a struct nvme_sqe or nvme_poll’s state on the issuer’s stack, DMA memory, a scsi_xfer) an enum (NvmeCookie) in a Cell, the stack objects held by value (the poll loop reads its state back out of the cookie), the others as &'static; a callback given the wrong variant panics no pointer to a stack frame is left in a ccb that outlives the call (a completion after a timed-out poll finds the restored cookie, not a dead frame), and each callback states what it expects
a softc member that is a table of function pointers the attach fills (struct bus_space sc_bus, struct machine_pci_chipset sc_pc, struct bus_dma_tag sc_dma) UnsafeCell<MaybeUninit<T>>, written once by the attach before the bus or child is attached, read through an unsafe accessor returning &'static T (the device is never detached) the softc is zeroed memory (M_ZERO), which is no valid fn pointer; the one write and the lifetime are where the unsafe lives
__packed USB descriptors and requests of uByte/uWord members, cast from buffers ((usb_interface_descriptor_t *)p) #[repr(C)] structures of u8/[u8; N] marked unsafe trait UsbWire (alignment 1, any bytes valid); usb_wire_at::<T>(bytes, off) is the bounds-checked cast; UGETW/USETW are ugetw/usetw on the [u8; 2] as ScsiWire: the slice carries the wTotalLength the C walks against
a host controller’s pipe or xfer that embeds struct usbd_pipe/usbd_xfer first, sized by bus->pipe_size or allocated by allocx unsafe trait UsbdHcPipe/UsbdHcXfer (repr(C), base first); usbd_bus_set_hc_types::<P, X>(bus) records the sizes and TypeIds; pipe.hc::<P>()/xfer.hc::<X>() check before the cast the size alone does not name the type (as SrWorkunitExt)
struct usbd_device *, usbd_pipe *, usbd_xfer * passed everywhere and freed by usb_free_device/usbd_close_pipe/usbd_free_xfer &'static T at the API, members Cell<Option<&'static T>>, the freeing functions unsafe fn(NonNull<T>) the C’s lifetime rules (detach before free) are the contract; NonNull at the free names the one unsafe step
a union at the user/kernel boundary whose members differ in size and that drivers fill member by member (mixer_devinfo.un, mixer_ctrl.un) a #[repr(C, align(4))] newtype over a byte array of the union’s size, with a view per member (un.e()/un.e_mut(), un.v(), un.ord()/un.set_ord(), un.value_mut()) cast in one private helper over plain-integer view types a Rust union written through a smaller member leaves the other bytes uninitialised, and the structure must be AbiPod (every byte initialised) to be copied out; the views keep the C member names
struct hid_data * from hid_start_parse(desc, len, kind) over a descriptor, ended by hid_end_parse Box<HidData<'_>> borrowing the descriptor slice, dropped (or passed to hid_end_parse) at the end; hid_get_item(&mut d, &mut item) the parse state is scratch the C mallocs per call; ownership says when it ends, the lifetime ties it to the descriptor the C keeps raw pointers into
a lookup that returns a report ID or -1 (hid_find_report, hid_get_id_of_collection) Option<u32> -1 is ERESTART in Errno, and “no such report” is not an error
a struct embedded in a bus driver’s softc that holds a malloced array, a struct timeout and function pointers (struct hidkbd) all members Cells with Hidkbd::new() as the zero state and a slice accessor over Cell<*mut T> + count; the timeout argument is the address of the embedded struct the owner’s softc is zero-allocated and the C changes every member under the kernel lock
a driver ioctl hook returning 0, an errno or -1 for “not mine” (wskbd_accessops.ioctl, hidkbd_ioctl) Result<bool, Errno> with Ok(false) for -1 the ttioctl convention
a member of a shared MI structure that one driver’s IPL_MPSAFE interrupt touches while the rest of the stack holds the kernel lock (usbd_bus.use_polling, dying, no_intrs from xhci_intr) a newtype over a relaxed atomic with Cell’s get/set (UsbdBusFlag, UsbdBusCounter), so the other users’ code is unchanged the C’s plain accesses compile to the same loads and stores; a Cell read on another CPU would be a data race in Rust’s model
a table of callbacks a library keeps for a host driver (struct ac97_host_if { void *arg; int (*read)(void *, ..); .. }), embedded in a zeroed softc a Copy struct of Option<unsafe fn(*mut c_void, ..)> plus arg; the library stores a copy taken at attach the softc must stay valid as all-zero bits; the host never changes the table after attaching, so the copy equals the C’s pointer; each call site states in // SAFETY: that arg is the host’s own
a const table of structs with void *info plus sizeof (ac97_source_info, WRAP(ac97_on_off)) two Option<&'static T> members (one per concrete type) and const fn builders (class_ent, value_ent, enum_ent, menum) that fill the nested statics the pointer’s type is known per entry, the copy length is the type’s size, and the tables stay statics with no allocation
a struct device-headed child softc that a bus driver stores by struct * in an array and casts back (uhidev.sc_subdevs[repid], struct uhidev first in ukbd_softc) the child softcs are #[repr(C)] Softcs with the parent’s head type (Uhidev) first; the array is Cell<Option<NonNull<Uhidev>>> entries from mallocarray, None for NULL, read through a slice accessor a pointer to the head is a pointer to the whole softc (the Softc contract), and the table is shared state under the kernel lock
a driver callback taking (void *buf, u_int len) (uhidev.sc_intr) fn(&Uhidev, &mut [u8]) the slice carries the length, and the caller clamps it to the buffer
a function returning an actlen or -1 for failure without an errno (uhidev_set_report, uhidev_get_report) Result<usize, Errno> with Err(EIO) the failure is not a length
a goto out1/out2/out3 cascade of cleanups (uhidev_open) nested labelled blocks ('out1: { 'out2: { ... break 'out1 ... } cleanup2 } cleanup1) the same fall-through order with no unsafe and no duplicated cleanup
the AML interpreter’s object graph: struct aml_value (reference counted by hand with aml_addref/aml_delref, its type and union changed in place through every pointer that shares it) and the struct aml_node tree (parent pointers, SIMPLEQ of children, value->node back links) in plain globals (aml_root, aml_regionspace[]) AmlValueRef = Rc<AmlValue> with the type and union one enum (AmlObj) in a RefCell, AmlNodeRef = Rc<AmlNode>, Weak for parent and value->node; the globals in AmlGlobal<T>, an unsafe impl Sync wrapper whose only accessor asserts KERNEL_ASSERT_LOCKED(); borrows last one access, results are copied out the graph is shared and mutated in place, so ownership must be counted; Rc’s non-atomic count is sound because every path to the interpreter runs under the kernel lock in OpenBSD (the acpi thread, acpi_interrupt without IPL_MPSAFE, non-MPSAFE tasks and timeouts; checked at the pin), and Arc would buy nothing while the kernel lock serialises it; Weak makes a deleted node read as NULL through the values that outlive it, which the C does by hand in aml_delchildren
uint8_t * cursors into firmware byte code (scope->pos, v_method.start, v_nameref) compared with < and read with aml_get8..64 AmlPtr { blob: &'static [u8], off } (Copy): add, at, get16, diff, ordering by address; a read past the table yields 0 the parser keeps the C’s pointer arithmetic and comparisons, and a malformed table cannot make it read outside the mapped bytes
a structure a chip’s SCRIPTS processor and the driver share in DMA memory, with members both write (struct siop_common_xfer: the tables and messages the chip reads, the msg_in/status bytes it writes) #[repr(C)] of Cells at the C’s offsets (asserted against the microcode’s A_t_*), reached as &'static over the DMA page; every access dma_get/dma_set, a read_volatile/write_volatile of the cell (dev/ic/siopvar_common.rs) the compiler may neither keep a byte the chip wrote nor move a table store past the register write that starts the chip; the cells keep the accesses safe and the structure reads like the C
a table generated by a host tool and #included (siop.out, made by ncr53cxxx from siop.ss) the generated file transcribed once by a script into statics and consts with the C names (sys/dev/microcode/siop/siop.rs), and a just test-ref test that compares every table and define with the .out one fixed file needs no build-time generator; the reference test catches any drift from the pinned tree
a firmware table copied with malloc(sizeof(*entry) + len) and then read through a larger structure than some revisions have (acpi_maptable’s copy read as struct acpi_fadt) a Vec<u8> copy zero-padded to at least the largest structure read through it, then leaked for the kernel’s lifetime the packed structure is read in bounds; the zeros are what an older revision’s missing fields mean
a void *arg that keeps a reference-counted node alive for a callback registered for good (acpi_set_gpehandler(sc, idx, acpi_gpe, node, ...)) Rc::into_raw(node.clone()) as the argument, never taken back; the callback borrows it with ManuallyDrop::new(Rc::from_raw(arg)) the registration owns one count, as the C’s pointer does, and each call leaves it untouched
a softc whose first member is a struct not valid as zeroes and cast back from it (struct ioapic_softc { struct pic sc_pic; ... }, (struct ioapic_softc *)pic) #[repr(C)] softc with the device first and the embedded struct as UnsafeCell<MaybeUninit<Pic>>, written once by attach before it is handed out; the cast back is one unsafe fn that subtracts offset_of!(Softc, field) config_make_softc zeroes the softc, which a &str or fn member cannot be; the offset is computed, not assumed to be 0
x86-only code that OpenBSD keeps in a machine-independent directory and that includes x86 headers (dev/acpi/acpimadt.c, acpiprt.c: <machine/i82093var.h>, mpbiosvar.h) a machine contract named after the header (machine::mpconfig::MpConfig): an associated type for the x86 struct (type Ioapic), associated constants for the header’s values, the functions the driver calls, and the structs the driver builds (MpBus, MpIntrMap) defined in the contract; other machines implement it as “none” (NoIoapic, an uninhabited type, constants 0) and never configure the driver the C file stays one .rs in dev/acpi, generic code never names arch::amd64, and arm64 compiles it as it compiles the FDT drivers on amd64
a vendor shared-code int32_t status (if_em_hw.c: E1000_SUCCESS, -E1000_ERR_PHY, the positive E1000_BLK_PHY_RESET/E1000_DEFER_INIT) with uint16_t * out parameters that callers often read after ignoring the status Result<_, i32> whose Err is the C’s non-zero value unchanged (? is if (ret_val) return ret_val;); the out parameters stay &mut the codes are not errnos and some positive ones are verdicts callers compare against; keeping &mut keeps the C’s “unchanged on failure” for the many callers that ignore the status
functions a header declares for a shared-code file but the driver file defines (em_pci_set_mwi, em_read_pci_cfg, … in if_em_hw.h, defined in if_em.c) a struct of fn pointers (EmPciOps) the driver passes when it builds the shared state (EmHw::new(back, &OPS)), and free functions of the C names in the shared module that call through it the shared module compiles and is tested before the driver exists, with no generic parameter threaded through 150 functions
a structure the C mutates through the void * cookie its operations receive, while one operation calls another with the same cookie (struct rasops_info under rasops_vcons_putchar -> ri_putchar) every field a Cell (Cell<Option<fn>> for the hooks, Cell<Table> for a Copy operation table), the operations unsafe fns over the cookie that make a shared & of it; same-named methods call the hooks with the structure as cookie (ri.ri_putchar(row, ...)) a shared reference can be re-derived from the cookie at every nesting level without aliasing a &mut; zero-valid, so it can live in a softc
a linear frame buffer reached through bus_space_vaddr and written as memory (*(int32_t *)dp = clr, memmove, memset in rasops) single pixels and words with write_volatile/read_volatile helpers (fb_w32, fb_r32, …), spans with ptr::copy/write_bytes the device memory rule (volatile) for each store, while the bulk moves stay the C’s memmove/memset
a font header (spleen8x16.h: a struct wsdisplay_font and its non-const glyph array) a module generated from the header with one array row per C line, the bit drawing kept as its comment, both in StaticCells, the font pointing at its glyphs through StaticCell::as_ptr wsfont_lock may reverse a font in place, as the C writes its arrays; the generated rows diff against the header line by line
a C enum whose members are compared with numbers read from hardware and used as numbers (em_cable_length, em_igp_cable_length) a type alias (EmCableLength = u16) and the members as constants with the C names a Rust enum cannot hold the unlisted values the register yields; members that are only tags stay Rust enums
a driver structure the C changes only under the kernel lock (sc->hw, struct em_hw) while its MP-safe paths (send queue, interrupt ring work) read a member that never changes after attach and pass it to register macros (E1000_READ_REG(&sc->hw, ...)) the structure in an UnsafeCell<MaybeUninit<T>> handed out by a guard (sc.em_hw()) that asserts the kernel lock and panics on a second live borrow; the unlocked paths use a second, read-only copy holding what the register access needs (sc.regs(): back and mac_type), written once before the interrupt is established; the few members an unlocked path reads that do change are mirrored into atomics when the guard drops a &mut taken under the lock and a & taken in an interrupt on another CPU would alias; the C’s lock discipline becomes the guard’s check, and the register view needs no lock because nothing in it changes
a pointer member that points at one of several tables the same structure owns, or is NULL (u_int *chartab_G[4] at isolatin1tab, decgraphtab, … in struct wsemul_vt100_emuldata), and is saved and restored by value an Option<enum> naming the table (Option<Vt100Chartab>), looked up through a method (chartab(slot)) that yields None for NULL or for a table that could not be allocated a pointer into the structure itself cannot be a Rust reference, and the tag cannot dangle when a table is freed; the copies (savedchartab_G) stay plain Copy values
an operation that returns a count and a pointer into its own state (translate(cookie, layout, ks, &dp) pointing into edp->translatebuf, or at a static string) the caller passes the buffer (buf: &mut [u8; WSEMUL_TRANSLATE_SIZE]) and the operation returns &[u8] borrowed from it or from a 'static string a slice returned from behind a raw cookie would have no lifetime to borrow from; the caller’s buffer gives it one, and the bytes are read at once, as the C’s caller does
a macro that wraps each driver call in shared bookkeeping, with the call as an argument (WSEMULOP(rc, edp, was, rutin, args): skip the calls a retried output already made, count the ones that succeed) a function taking the call as a closure, wsemulop(&mut edp.abortstate, \|\| eo.putchar(row, col, c, attr))?, the arguments computed into locals first the closure is the call the C pastes in; copying the Copy operations table out (let eo = edp.emulops) and the arguments into locals keeps the closure from borrowing the state the bookkeeping changes
a callback into a file that is not ported yet, which the ported code’s tests must observe (wsdisplay_emulbell, wsdisplay_emulinput called by the terminal emulations) the real name is a visible stub (unported!) the later port replaces; the caller imports it under #[cfg(not(test))] and a recording double of the same name from the module’s testutil under #[cfg(test)] the tests keep working when the stub becomes the real function, which needs a whole display and tty the tests do not build
registers named by the fields of a structure that only lays out an index space, read through macros taking the field name (vga_ts_write(vh, syncreset, 1) over offsetof(struct reg_vgats, syncreset)) the structure as #[repr(C)] (or packed) of u8s, and macro_rules! with the C’s name and arguments over core::mem::offset_of!, re-exported with pub(crate) use (vgavar.rs, pcdisplayvar.rs) the call sites read as in C and the indices stay the C’s layout; size_of gives the save areas vga_pci.c fills
a static table the C sorts in place on first use, guarded by a flag (compose_tab and compose_tab_inorder in wskbdutil.c) the C’s table as a const, and the C’s sort as a const fn run at compile time into a static (COMPOSE_TAB_SORTED = compose_tab_sorted(COMPOSE_TAB)); the lookup is the C’s no mutable static and no first-use race; the order is the one the C’s insertion sort produces, and a host test checks it
structures that start with a common header and are cast to and from it (struct wsevsrc at the head of struct wskbd_softc and struct wsmux_softc, its struct device first) #[repr(C)] structures with the header first, all-zero valid (Softc), and named casts (Wsevsrc::of_device, WsmuxSoftc::of_evsrc) whose # Safety says which kind of object the pointer is the methods table (me_ops) is what tells the kinds apart, as in C; a trait object would need a second allocation the C does not have
a generated C file kept byte for byte, licence block included (ukbdmap.c, with trailing blanks in its header) the Rust generated from the C by a script, the module declared #[rustfmt::skip] in its mod.rs rustfmt would trim the licence’s blanks and reflow the tables; the reference test compares every keysym with the C instead
a large nested structure in a softc that a driver’s interrupt path and the ioctls change in place at spltty through struct device * casts, and whose helpers pass pointers to its parts around (struct wsmouseinput in struct wsmouse_softc) UnsafeCell<WsmouseInput> of plain fields, handed out by sc.input() as a guard (InputGuard, Deref/DerefMut) that panics on a second live borrow; the C-named functions a driver calls take &Device and run methods of the input, so one function calling another does not take the guard twice Cells would turn every input->motion.pos.dx of 3,600 lines into get/set pairs and lose the &mut Position the helpers take; the guard is the C’s spltty discipline made checkable, like em_hw without the kernel-lock assert (a mouse’s reports come from its parent’s interrupt)
a pointer that selects one of several sibling structures for a long-lived object (tpad_touch.pos: &input->motion.pos or &input->mt.slots[i].pos; tp->t into tp->tpad_touches) an enum or index (TouchPos::Motion, TouchPos::Slot(i), t: usize) and an accessor that resolves it against the owner (pos(input, t), tp.t()) a stored reference into the same allocation, or into one that wsmouse_mt_init may replace, cannot be expressed; the index borrows only when it is read
a graph of malloced nodes linked through their own next fields, where one node is on several lists at once and a source shared by several destinations is linked once (struct uaudio_unit’s unit_next, src_next, dst_next, src_list, dst_list, clock), built at attach into a zeroed softc a Vec arena in a configuration struct allocated by the attach (UaudioConf, a leaked Box behind Cell<Option<NonNull<_>>>, freed by the detach), the links Option<usize> indices; the singly linked lists the C only walks and inserts into (sorted or appended) are Vecs in list order indices keep the C’s sharing and list order exactly (the naming and the mixer indices depend on them) without a borrow per link; the softc stays all-zero valid, and the run-time state the USB and audio(4) call-backs share stays Cells in it
a driver’s table of interface functions over the softc’s registers (struct ipmi_if: kcs_sendmsg, smic_recvmsg, bt_probe, … all reaching the chip through bmc_read/bmc_write) the table is generic over a trait of the softc’s register access and state (IpmiIf<S: IpmiBmc>), its instances associated consts (IpmiIf::<S>::KCS); the kernel’s statics are IpmiIf<IpmiSoftc>, the host tests build IpmiIf<Fake> over a simulated chip the protocol state machines are the part worth testing and talk to real registers; one generic copy serves both, monomorphised, as the Raid5Io row does for I/O planning