os_pipe.rs

A cross-platform Rust crate for opening OS pipes with safe, non-inheritable PipeReader and PipeWriter types.

Library
Cargo
v1.2.3
115stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
33/100Needs Attention
Development Activity0
Maintenance0
Community60
Maturity60
Momentum12

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
63/100Good
Architecture75
Code Quality80
Innovation40
Learning Curve55

os_pipe is a small cross-platform Rust crate for opening OS-level pipes — the kind created by pipe/pipe2 on Linux and CreatePipe on Windows — without reaching for platform-specific FFI directly. It returns a PipeReader/PipeWriter pair backed by std::fs::File, both of which implement Into<Stdio> so they drop straight into std::process::Command’s stdin/stdout/stderr for wiring up child process I/O, including joining a child’s stdout and stderr into a single combined stream.

Beyond raw pipe creation, the crate exposes dup_stdin, dup_stdout, and dup_stderr helpers that duplicate the current process’s standard handles as owned pipe ends, useful when extra copies of the parent’s streams are needed without disturbing the originals. Every pipe it creates is non-inheritable by default (CLOEXEC on Unix, non-inheritable handles on Windows), preventing children from silently holding a copy of a pipe end they weren’t explicitly given. As of Rust 1.87, similar functionality is available via std::io::pipe, but os_pipe remains useful for supporting older compiler versions, and its README documents at length the deadlock pitfalls that come with pipe-based I/O.

What You Get

  • Cross-platform pipe() - Opens a non-inheritable OS pipe on both Unix and Windows behind one function call, backed by pipe2()/CreatePipe respectively.
  • Stdio-ready types - PipeReader and PipeWriter implement Into<Stdio>, so they plug directly into Command::stdin/stdout/stderr.
  • Standard stream duplication - dup_stdin, dup_stdout, and dup_stderr return owned duplicates of the current process’s standard handles.
  • try_clone support - Both pipe ends implement try_clone, letting a single pipe be fanned out to multiple owners, e.g. combining a child’s stdout and stderr.
  • Deadlock-focused documentation - The README explains the two classic pipe deadlock patterns (unclosed writers, full buffers) with worked examples.

Common Use Cases

  • Joining child stdout/stderr - Cloning a pipe writer and setting it as both stdout and stderr on a Command to interleave a child process’s output into one stream.
  • Custom child process I/O plumbing - Building tools that spawn subprocesses and need direct, low-level control over pipe lifetimes instead of relying on Stdio::piped().
  • Duplicating parent stdio for children - Passing dup_stdout()/dup_stderr() results to a child so it shares a synchronized copy of the parent’s streams.
  • Supporting older Rust toolchains - Using os_pipe as a compatibility shim on projects that can’t yet require Rust 1.87’s std::io::pipe.

Under The Hood

Architecture The crate keeps a tiny, focused surface: lib.rs defines the public PipeReader/PipeWriter wrapper types (both thin newtypes over std::fs::File) and the pipe(), dup_stdin(), dup_stdout(), and dup_stderr() functions, all of which delegate to a sys module whose implementation is switched at compile time via #[cfg(windows)]/#[cfg(not(windows))] #[path] attributes pointing at unix.rs or windows.rs. This isolates every platform-specific syscall (pipe2/fcntl on Unix, CreatePipe on Windows) inside the sys module while the public API, trait implementations (io::Read/Write, Into<Stdio>, AsRawFd/AsRawHandle, AsFd/AsHandle), and documentation stay platform-agnostic; changing the core File-backed representation would touch both platform modules but not the public API shape.

Tech Stack Built for Rust 2021 edition with an MSRV of 1.63, published to crates.io with docs hosted on docs.rs. It depends on libc (^0.2.62) on non-Windows targets for pipe2/fcntl, and on windows-sys (pinned to a wide >=0.28, <=0.61 range with the Win32_Foundation, Win32_System_Pipes, and Win32_Security features) on Windows for CreatePipe. There is no async runtime, no build script, and no other runtime dependency; a GitHub Actions workflow (referenced by the README’s badge) runs the test suite.

Code Quality Tests live inline under #[cfg(test)] mod tests in lib.rs and exercise both the common path (writing and reading data of varying sizes, try_clone, Debug formatting) and cross-process edge cases (verifying pipes are genuinely non-inheritable, that parent-handle duplication doesn’t close the originals) by shelling out to small helper binaries in src/bin/ (cat, swap, cat_both) built just for the test run. All public functions return io::Result and propagate OS errors via io::Error::last_os_error() rather than panicking or swallowing failures; naming follows idiomatic Rust conventions throughout. No linter configuration file is present in the repo beyond standard rustfmt conventions, but CI is wired up via GitHub Actions.

What Makes It Unique The crate itself is intentionally unglamorous — a faithful, correct cross-platform wrapper around two very different native pipe APIs — and its own README candidly notes that Rust 1.87’s std::io::pipe has since absorbed most of its core functionality, making os_pipe primarily a compatibility layer for older toolchains today. Its lasting value is in getting the non-inheritability and cross-process ownership semantics right on both platforms, and in documenting — clearly and at length — the two deadlock patterns (forgotten writers, full pipe buffers) that trip up nearly every developer who first works with OS pipes directly.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search