pin-project-lite

A lightweight Rust macro crate for safe pin projection, implemented with declarative macros instead of proc-macros.

Library
Cargo
v0.2.17
278stars
Apache License 2.0

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
58/100Fair
Development Activity48
Maintenance56
Community48
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
76/100Good
Architecture80
Code Quality85
Innovation72
Learning Curve65

pin-project-lite provides the pin_project! macro, which generates a safe projection type for a struct or enum so that individual fields can be accessed as Pin<&mut T> or plain &mut T references depending on whether they’re marked #[pin]. It solves the same core problem as the pin-project crate — writing self-referential or !Unpin futures and other pinned data structures without unsafe code — but is implemented entirely with macro_rules! declarative macros rather than a proc-macro crate.

That implementation choice is the crate’s entire reason to exist: for crates that don’t already depend on syn/quote/proc-macro machinery elsewhere in their dependency graph, pin-project-lite avoids adding that (relatively heavy) compile-time dependency chain. The tradeoff is a narrower feature set than full pin-project — no custom Unpin opt-outs via UnsafeUnpin, no tuple struct/variant support, and much less helpful compiler error messages on misuse.

What You Get

  • The pin_project! macro, applied to a struct or enum definition, generating a .project() method returning a projection type
  • Per-field #[pin] attribute to mark which fields should project to Pin<&mut T> versus a plain &mut T
  • Enum support via a named projection type (e.g. #[project = EnumProj])
  • The same safety guarantees as full pin-project — no unsafe code required in downstream crates
  • Zero proc-macro dependency chain, keeping compile times and dependency trees lean for crates that don’t already pull in syn/quote

Common Use Cases

  • Implementing custom Future/Stream types by hand that hold both pinned (self-referential) and unpinned fields
  • Any low-level async runtime or combinator crate wanting pin projection without adding a proc-macro dependency chain
  • Libraries with strict compile-time or dependency-tree budgets that still need safe pinned-field access
  • Learning or prototyping pin projection where full pin-project’s more helpful error messages aren’t yet needed

Under The Hood

Architecture: The entire implementation lives in a single src/lib.rs (1,772 lines), built around the pin_project! declarative macro. Rather than parsing arbitrary Rust syntax with a proc-macro parser, it uses macro_rules!’s token-tree matching to recognize struct/enum definitions, per-field #[pin] attributes, and optional projection-type naming, then emits a projection struct/enum plus a .project() method and the necessary (encapsulated, checked) unsafe pinning logic. Because macro_rules! matching is far less flexible than a full parser, the macro supports a deliberately constrained input grammar — no tuple structs/variants, no custom Unpin opt-out — which keeps the matching rules tractable.

Tech Stack: Pure Rust, edition 2018, MSRV 1.37, zero dependencies of any kind at the crate level (dev-dependencies only, for its own test suite: macrotest, rustversion, static_assertions, trybuild). A [workspace.lints] block shared across the author’s (taiki-e) crates enables clippy::pedantic plus several rustc lints, and the workspace includes tests/no-core, tests/no-std, and tests/lint sub-crates for isolated environment testing.

Code Quality: The crate uses trybuild and macrotest for compile-fail and macro-expansion snapshot testing respectively, which is the standard rigorous testing approach for macro crates where correctness means “generates exactly the expected code” as much as “compiles.” undocumented_unsafe_blocks is enabled as a workspace lint, forcing every unsafe block the macro generates to carry a safety-justification comment — meaningful given that the entire crate’s value proposition is guaranteeing safety around raw pinning. A DEVELOPMENT.md documents the contribution/testing workflow.

API Design: The macro’s surface is intentionally minimal — wrap a struct/enum in pin_project! { ... }, mark fields #[pin], call .project() — mirroring pin-project’s API closely enough that the README documents it as a drop-in-compatible subset. The crate is explicit in its own docs about what it doesn’t support (tuple structs, UnsafeUnpin, helpful macro error messages), directing users to the full pin-project crate the moment their needs exceed this one, which is an unusually honest API-scoping decision for a widely-depended-on crate.

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