The libc crate provides Rust bindings to standard C library functions, allowing you to call low-level system APIs like fork, open, or getpid directly from safe Rust code. You use it by adding the crate to your Cargo.toml and calling its functions via unsafe blocks, as the crate cannot guarantee memory safety for external C calls.
Add the dependency to your Cargo.toml:
[dependencies]
libc = "0.2"
Here is a practical example calling getpid() to retrieve the current process ID. Since libc::getpid() returns a pid_t (which is a C type alias), you cast it to a native Rust integer:
use libc::getpid;
fn main() {
// Wrap the FFI call in an unsafe block
let pid = unsafe { getpid() };
// pid_t is usually i32, but cast explicitly for clarity
println!("Current process ID: {}", pid as i32);
}
For more complex interactions, such as reading a file using open and read, you must manage file descriptors and buffers manually. The following example opens a file, reads the first 10 bytes, and closes the descriptor:
use libc::{open, read, close, O_RDONLY, O_CREAT, S_IRUSR, S_IWUSR};
use std::ffi::CString;
use std::os::unix::io::RawFd;
fn main() {
let path = CString::new("/tmp/test.txt").unwrap();
// Open file (create if not exists)
let fd = unsafe {
open(
path.as_ptr(),
O_RDONLY | O_CREAT,
S_IRUSR | S_IWUSR,
)
};
if fd < 0 {
eprintln!("Failed to open file");
return;
}
let mut buffer = [0u8; 10];
let bytes_read = unsafe {
read(fd, buffer.as_mut_ptr() as *mut libc::c_void, buffer.len())
};
if bytes_read > 0 {
let content = &buffer[..bytes_read as usize];
println!("Read: {:?}", content);
}
// Always close the file descriptor
unsafe { close(fd) };
}
Key points to remember: always wrap libc calls in unsafe blocks because the compiler cannot verify the safety of the underlying C code. Ensure you handle error codes (usually negative integers) returned by these functions, as they do not panic on failure like standard Rust library functions. Use std::ffi::CString for string arguments to ensure null-termination, which C functions expect. Finally, remember that libc types like c_int, c_void, and size_t map to platform-specific Rust types, so casting is often necessary when integrating results into safe Rust logic.