Guides using bun_sys for system calls and file I/O in Rust. Use when implementing file operations, opening fds, or any syscall path instead of std::fs or libc.
Install
npx skillscat add oven-sh/bun/rust-system-calls Install via the SkillsCat registry.
We need to produce a 2-3 sentence plain-text summary, objective, factual, no marketing language, no superlatives, no calls to action. Must be natural prose, no bullet points, headings, markdown. At most 60 words. Must not wrap in quotes. Only the summary text. We need to explain: what this skill does, what problem it solves, when to use it. So something like: "The skill provides guidance on using bun_sys for system calls and file I/O in Rust, enabling safe cross-platform operations with proper error handling.
System Calls & File I/O in Rust
Use bun_sys instead of std::fs or raw libc for cross-platform syscalls with proper error handling.
bun_sys::File (Preferred)
For most file operations, use the bun_sys::File wrapper. It owns the fd and closes on Drop.
use bun_sys::{File, Fd, O};
let file = File::openat(Fd::cwd(), b"path/to/file", O::RDONLY, 0)?;
let mut buf = vec![0u8; 4096];
let n = file.read_all(&mut buf)?; // loops until EOF or full
// `file` closes on Drop.Complete Example
use bun_sys::{File, Fd, O};
pub fn write_file(path: &[u8], data: &[u8]) -> Result<(), bun_sys::Error> {
let file = File::openat(Fd::cwd(), path, O::WRONLY | O::CREAT | O::TRUNC, 0o664)?;
file.write_all(data)?;
Ok(())
}Why bun_sys?
| Aspect | bun_sys | std::fs / libc |
|---|---|---|
| Return Type | Maybe<T> with rich Error |
io::Error (lossy) |
| Windows | Full support with libuv fallback | Incomplete/POSIX-ish |
| Error Info | errno, syscall tag, path, fd | errno only |
| EINTR | Automatic retry | Manual handling |
| Paths | &[u8] (WTF-8 safe) |
&Path (UTF-8 lossy) |
Error Handling with Maybe
bun_sys functions return Maybe<T> = Result<T, bun_sys::Error>. Propagate with ?; convert to a JS exception via bun_sys_jsc::ErrorJsc::to_js:
use bun_sys_jsc::ErrorJsc;
use bun_sys::{File, Fd, O};
let file = match File::openat(Fd::cwd(), path, O::RDONLY, 0) {
Ok(f) => f,
Err(err) => return Ok(err.to_js(global)?),
};bun_sys::Error carries errno, syscall: Tag, and path: Box<[u8]>. To branch on errno:
match bun_sys::unlink(path) {
Ok(()) => {}
Err(e) if e.errno() == bun_c::ENOENT => {} // already gone
Err(e) => return Err(e),
}Key Types and Functions
Fd(bun_core::Fd) — cross-platform file descriptor.Fd::cwd(),Fd::stdin()/stdout()/stderr(),fd.close().File::open(path: &ZStr, flags, mode)/File::openat(dir: Fd, path: &[u8], flags, mode)/File::make_open(...)(creates parent dirs) /File::create(dir, path, truncate)file.read(buf)/read_all(buf)/read_to_end()/read_to_end_small()/write(buf)/write_all(buf)bun_sys::open,read,write,pread,pwrite,stat,fstat,lstat,mkdir,unlink,rename,symlink,chmod— free fns overFd- Open flags:
bun_sys::O::RDONLY,O::WRONLY | O::CREAT | O::TRUNC, etc.
Path Buffers
Use bun_paths for joining/normalization and the path-buffer pool to avoid 64 KB stack allocations on Windows:
use bun_paths::{path_buffer_pool, resolve_path::{self, platform}};
let mut buf = path_buffer_pool::get();
let joined = resolve_path::join_string_buf::<platform::Auto>(&mut *buf, &[dir, name]);
let file = File::openat(Fd::cwd(), joined, O::RDONLY, 0)?;Common Mistakes
- Don't
.unwrap()abun_sysresult that user input or the OS can cause to fail at runtime — return the error. - Don't use
std::fs::File— it loses the syscall tag and path needed for Node-compatible error objects. - Don't allocate
PathBufferon the stack in hot paths — usepath_buffer_pool::get(). - Don't forget
Dropalone closes aFile— neverfile.fd().close()while theFileis still live (double close).
See src/CLAUDE.md for the full bun_core/bun_sys reference.