rust-lang/libc: raw FFI bindings for Rust, and when to reach past the std facade
Raw bindings to platform APIs for Rust
At a glance
- What is it?
- libc gives Rust code the platform's own type definitions, constants and function headers under a single crate root. It is the crate you add when std does not expose the syscall, errno or struct you need, and its v1.0 branch is now separate from the 0.2 line most projects depend on.
- Who is it for?
- Adopt libc when you need a platform type, constant or function header that std does not expose, and pin the dependency to the 0.2 line unless you are prepared to track a pre-release. Do not adopt it for Windows API work: the README states Windows API bindings are not included and points to crates like windows-sys.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The gap libc fills between Rust std and the C ABI
Rust's standard library wraps a large amount of the operating system, but it does not wrap all of it. When you need `c_int`, the constant `EINVAL`, or the declaration of `malloc`, there is nowhere in std to get it. libc is the crate that supplies those declarations. The README describes the scope plainly: type definitions, constants and function headers, exported under the crate root so every item is reachable as `libc::foo`. The values and types match the platform the crate is compiled for, which is the point. A `struct stat` on Linux and a `struct stat` on macOS are not the same layout, and libc does not pretend otherwise.
The audience is narrow but deep. People writing unsafe blocks against a syscall, porting a C library, or building a higher-level wrapper crate such as nix or a filesystem library. If your code never leaves safe Rust and never touches a raw file descriptor, you probably never call libc directly, even though it is likely somewhere in your dependency tree. That distinction matters when you read the question of whether Rust depends on libc: at the language level it does not, but at the crate level, plenty of the ecosystem does.
How the bindings are selected, and why the crate root is flat
The mechanism is conditional compilation driven by the target triple, with a build script in the repository root. The repository ships a build.rs and a src directory whose contents are gated per platform, and the published documentation is generated per target: the package metadata on docs.rs lists a default target of x86_64-unknown-linux-gnu plus a long list of additional targets, from aarch64-apple-darwin and aarch64-pc-windows-msvc through powerpc64le-unknown-linux-musl, s390x-unknown-linux-gnu and sparcv9-sun-solaris. When you open the docs for a specific platform, you see only what exists there.
The flat namespace is a deliberate trade-off. Everything lives at `libc::`, which makes lookup predictable and keeps generated code simple, but it also means the crate root is enormous and names from different platforms sit side by side in the source. There is no module tree to browse by subsystem. In practice you search the docs rather than read them, and IDE completion on `libc::` is not a pleasant way to discover an API. The README points to the associated RFC for the design rationale, and that RFC is the right place to look if the flat layout surprises you.
Adding libc to a project and calling into the platform
The README gives the dependency line directly. Add it to Cargo.toml with the 0.2 version requirement, which is what the currently published line uses:
[dependencies]
libc = "0.2"With that in place, a call into the platform is a normal unsafe block. The README names `malloc` as one of the function headers the crate provides, and the crate exports all items under the crate root as `libc::foo`, so the call site is a direct path into that namespace. The same pattern applies to constants, for example comparing a returned error code against `libc::EINVAL` after a failed call.
One build detail is worth knowing before you start. The minimum supported Rust toolchain version is currently Rust 1.65, and the Cargo.toml records the same as rust-version. The README states that increases to the MSRV are allowed to change without a major release, to avoid a ripple effect in the ecosystem, and that a policy for when this may change is a work in progress. If you pin an old toolchain, a libc update can break your build without any semver signal.
The v1.0 branch split is the real upgrade decision
The repository currently carries two active branches. main targets the upcoming v1.0 release, and libc-0.2 carries the currently published version. The README instructs contributors that pull requests should target main by default and can be cherry picked to libc-0.2 once reviewed, and it states that new v0.2 releases will stop once v1.0 ships.
For a consumer this is the constraint that matters more than any individual binding. The Cargo.toml in the repository declares version 1.0.0-alpha.4, and the recent release list shows both 1.0.0-alpha.4 and 0.2.189 published on the same day in July 2026. That is a pre-release sitting alongside a stable line, not a migration that has happened. Depending on an alpha means accepting that the surface can move before 1.0 lands, and the README's own roadmap language gives no date. If your project needs a stable dependency today, the 0.2 line is the one the README documents for users, and the upgrade to 1.0 is a future event you will schedule rather than a change you can absorb silently.
Where libc is the wrong dependency
Windows is the clearest boundary. The README states that Windows API bindings are not included in this crate and directs readers looking for WinAPI bindings to crates like windows-sys. If your target is windows-msvc or windows-gnu and your work is against the Win32 surface rather than the C runtime, libc is the wrong crate, and the fact that it lists Windows targets in its docs.rs metadata does not change that. Those targets are there because the C runtime exists on them, not because the Windows API is bound.
A second boundary is abstraction level. libc is raw FFI: it gives you the declaration, not a safe wrapper, not a lifetime, not an error type. Every call is unsafe and every errno check is your responsibility. A crate that wraps the same syscalls in a safe interface will usually be the better choice for application code, and dropping to libc is what you do when the wrapper does not expose what you need. There is also a portability cost that is easy to underestimate: code written against libc constants compiles per platform, so a binding that exists on Linux may be absent or differently shaped elsewhere, and the compiler will tell you only when you build for that target.
nix and windows-sys as the two realistic alternatives
The comparison the README itself makes is windows-sys, and the difference is one of scope rather than quality. windows-sys binds the Windows API, which libc deliberately excludes. If you are writing Windows-specific code, that is the crate the project points you toward, and there is no overlap to weigh.
For Unix targets, the alternative most people actually consider is nix, which sits one level above libc and presents the same syscalls through safe Rust signatures with typed errors and owned file descriptors. The difference in approach is the whole argument: libc hands you the C declaration and leaves the unsafe block to you, while nix does the wrapping and the errno translation for you. Choosing libc means you want the raw declaration, either because nix does not cover the specific call, because you are writing the wrapper layer yourself, or because you are generating bindings and need the primitive rather than a hand-written API. Choosing nix means you would rather not write the unsafe block at all. Both are legitimate, and picking libc for application-level code is usually a sign that a wrapper was missing rather than a sign that raw bindings were needed.
Licence, contribution terms and the cost of staying current
The crate is dual licensed under Apache-2.0 and MIT, at your option, and the Cargo.toml records the same as "MIT OR Apache-2.0". Both licence files sit in the repository root. The README adds a contribution clause: unless you state otherwise, any contribution intentionally submitted for inclusion is dual licensed as above with no additional terms. That is the standard Rust ecosystem arrangement, and it means downstream users can pick either licence. This is a description of what the files say, not legal advice.
Maintenance cost is mostly about the toolchain rather than the code. The MSRV of Rust 1.65 can move without a major release, so a CI pipeline pinned to an old compiler can fail on a routine libc bump. The repository layout suggests the project takes verification seriously, with ci/verify-build.py named in the README as the source of truth for guaranteed build targets, plus libc-test, ctest and ctest-test directories, and a bors configuration for merging. That is a project with real infrastructure behind it. It is also a project where the interesting work is now happening on main, so read the CHANGELOG before bumping and check whether the change you want has been cherry picked to libc-0.2.
Editorial conclusion
Adopt libc when you need a platform type, constant or function header that std does not expose, and pin the dependency to the 0.2 line unless you are prepared to track a pre-release. Do not adopt it for Windows API work: the README states Windows API bindings are not included and points to crates like windows-sys. Before you commit, check that your target appears in ci/verify-build.py, confirm the MSRV of Rust 1.65 against your toolchain, and decide whether you are on main or libc-0.2.
Frequently asked questions
Does Rust depend on libc?
The language does not, but the crate ecosystem often does. libc provides the raw FFI bindings that many crates use to reach platform types, constants and function headers, and the README describes it as the definitions needed to interoperate with C code on each supported platform.
Can I use rust-lang/libc on Windows?
The README states that Windows API bindings are not included in the crate and suggests crates like windows-sys for WinAPI bindings. Windows targets do appear in the docs.rs target list, but that reflects the C runtime rather than the Windows API surface.
Which version of rust-lang/libc should I depend on?
The README tells users to add libc = "0.2", and the repository keeps that line on the libc-0.2 branch. The main branch targets the upcoming v1.0 release, which is currently published only as a pre-release, and the README says new v0.2 releases will stop once v1.0 ships.
What Rust version does rust-lang/libc require?
The minimum supported Rust toolchain version is currently Rust 1.65, and the Cargo.toml records the same as rust-version. The README notes that increases to the MSRV may happen without a major release, so an old pinned toolchain can break on a routine update.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/rust-lang-libc)