Пример
pin-project-lite 0.2.17: Enforce !Unpin on pin_project! structs using #[pin] PhantomPinned or #[project(!Unpin)] instead of untagged PhantomPinned
Проверенный пример — cargo pin-project-lite 0.2.17: Enforce !Unpin on pin_project! structs using #[pin] PhantomPinned or #[project(!Unpin)] instead of…
sha256:dca81589d2d6b9ea1e205efb1ea79821ea28d608e9f154452fc80711cb34dec0
Эта сеть предлагает одно: образец, который собирается. Она запустила его в песочнице и сохранила подписанную квитанцию. Она ничего не оценивает и ничего не гарантирует — собирается ли тот же код у вас, она не измеряла.
Сколько различных ключей подписи подали пройденную квитанцию контракта. Один — только автор; больше одного — значит, кто-то ещё тоже собрал. Ключ создаётся сам и не имеет зарегистрированной личности, поэтому считаются ключи, а не люди.
MIT-0
Свидетельства выполнения
Заявленное окружение и подписанные запуски разделены, чтобы вы точно видели, что этот образец запускал и где.
- Основа свидетельства
- Подписанный контракт пройден
- Квитанции проверки
- 3
- Ключи подписи, собравшие его
- 3
Заявленная среда
rust linux x64 rust rust cargo
Среды запусков проверки
| Окружение | Контракт | Этапы | Запуск |
|---|---|---|---|
| rust 1 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · cargo@1 |
2026-08-17 |
| rust 1 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · cargo@1 |
2026-08-18 |
| rust 1 · linux alpine/x64 · docker ed25519:c1973797be207ac4 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · cargo@1rust:1-alpine@sha256:a10e64dd139b… |
2026-09-08 |
Кейс
HOW- Цель
- Enforce !Unpin on pin_project! structs using #[pin] PhantomPinned or #[project(!Unpin)] instead of untagged PhantomPinned
- Пакеты
- Символы
-
- pin_project!
- Окружение
- rust
- Создан
- 2026-08-17T02:33:48Z
Контракт
- UntaggedPhantom<i32, String> implements Unpin despite containing a PhantomPinned field because pin_project! only bounds #[pin] fields
- Pin::new(&mut untagged) works safely on UntaggedPhantom because pin_project! generates an explicit Unpin impl
- TaggedPhantom requires #[pin] on PhantomPinned to propagate !Unpin to the struct
- AttributeNotUnpin uses #[project(!Unpin)] to suppress the automatic Unpin implementation entirely
- project() returns Pin<&mut T> for #[pin] fields and &mut U for unpinned fields
- project_ref() returns Pin<&T> for #[pin] fields and &U for unpinned fields
- project_replace replaces the struct in-place and moves out unpinned fields by value
Файлы
- .gitignore
- Cargo.lock
- Cargo.toml
- NOTES.md
- csx.json
- src/lib.rs
Исходный код
/target
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "cargo-pin-project-lite-api"
version = "0.1.0"
dependencies = [
"pin-project-lite",
]
[[package]]
name = "pin-project-lite"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[package]
name = "cargo-pin-project-lite-api"
version = "0.1.0"
edition = "2024"
[dependencies]
pin-project-lite = "0.2.17"
# pin-project-lite API Notes
## search_known_solution Result
`search_known_solution` for `pkg:cargo/pin-project-lite@0.2.17` returned `MATCH: COMPATIBLE` pointing to sample `sha256:70cb8cec09e074888556da7097e94d68ad979b471ca30473f3e2662c391dd7fd`, whose goal is:
> "Handle Tower 0.5 middleware composition ordering, poll_ready reservation and clone invalidation traps, buffer-timeout-loadshed queue interactions, Steer head-of-line blocking, and in-place retry policy mutation"
This is a Tower middleware composition sample that merely includes `pin-project-lite` as a transitive/shared dependency, rather than addressing `pin-project-lite`'s own macro and structural pinning traps.
## How the Trap Fails
Fails silently with a green build: an unannotated `_pin: PhantomPinned` field in `pin_project!` is ignored by the macro's generated `Unpin` bounds, leaving the struct `Unpin` and permitting safe `Pin::new` construction and moves where `!Unpin` was expected for soundness.
{"case":{"caseId":"case:sha256:d866dc92e3b8696ded89b3e45f7baf4ce37c75dca93fc6b3c51898c0e5a84b06","contract":["UntaggedPhantom\u003ci32, String\u003e implements Unpin despite containing a PhantomPinned field because pin_project! only bounds #[pin] fields","Pin::new(\u0026mut untagged) works safely on UntaggedPhantom because pin_project! generates an explicit Unpin impl","TaggedPhantom requires #[pin] on PhantomPinned to propagate !Unpin to the struct","AttributeNotUnpin uses #[project(!Unpin)] to suppress the automatic Unpin implementation entirely","project() returns Pin\u003c\u0026mut T\u003e for #[pin] fields and \u0026mut U for unpinned fields","project_ref() returns Pin\u003c\u0026T\u003e for #[pin] fields and \u0026U for unpinned fields","project_replace replaces the struct in-place and moves out unpinned fields by value"],"goal":"Enforce !Unpin on pin_project! structs using #[pin] PhantomPinned or #[project(!Unpin)] instead of untagged PhantomPinned","kind":"HOW","packages":["pkg:cargo/pin-project-lite@0.2.17"],"schemaVersion":1,"symbols":["pin_project!"]},"contractCommand":["cargo","test","--offline"],"environment":{"arch":"x64","ecosystem":"cargo","executionContext":"rust","language":"rust","os":"linux","packageManager":"cargo","runtime":"rust","schemaVersion":1},"license":"MIT-0","packages":["pkg:cargo/pin-project-lite@0.2.17"],"schemaVersion":1,"symbols":["pin_project!"],"verifierAdapter":"cargo@1"}
use core::marker::PhantomPinned;
use pin_project_lite::pin_project;
// 1. Untagged PhantomPinned:
// A naive model assumes adding `_pin: PhantomPinned` makes the struct `!Unpin`.
// However, `pin_project!` generates `impl<T: Unpin> Unpin for UntaggedPhantom<T, U> {}`
// checking ONLY fields tagged with `#[pin]`. Thus `UntaggedPhantom<i32, String>` is `Unpin`.
pin_project! {
pub struct UntaggedPhantom<T, U> {
#[pin]
pub pinned_data: T,
pub unpinned_data: U,
pub _pin: PhantomPinned,
}
}
// 2. Correctly tagged PhantomPinned:
// Tagging `#[pin]` on `PhantomPinned` forces `pin_project!` to require `PhantomPinned: Unpin`
// in the generated `Unpin` impl. Since `PhantomPinned: !Unpin`, the struct becomes `!Unpin`.
pin_project! {
pub struct TaggedPhantom<T> {
#[pin]
pub pinned_data: T,
#[pin]
pub _pin: PhantomPinned,
}
}
// 3. Explicit `#[project(!Unpin)]` attribute:
// Suppresses the automatic `Unpin` implementation entirely without requiring a `PhantomPinned` field.
pin_project! {
#[project(!Unpin)]
pub struct AttributeNotUnpin<T> {
#[pin]
pub pinned_data: T,
}
}
// 4. `#[project_replace = ...]` attribute:
// Enables replacing the pinned struct in place while moving out unpinned fields by value.
pin_project! {
#[project_replace = ReplaceProj]
pub struct Replaceable<T, U> {
#[pin]
pub pinned_data: T,
pub unpinned_data: U,
}
}
// Helper compile-time assertion function that requires T: Unpin
pub const fn assert_is_unpin<T: Unpin>() {}
#[cfg(test)]
mod tests {
use super::*;
use core::pin::Pin;
#[test]
fn test_untagged_phantom_is_unpin_trap() {
// Compile-time & runtime proof: UntaggedPhantom<i32, String> is Unpin!
assert_is_unpin::<UntaggedPhantom<i32, String>>();
let mut instance = UntaggedPhantom {
pinned_data: 42,
unpinned_data: String::from("initial"),
_pin: PhantomPinned,
};
// Because UntaggedPhantom<i32, String> is Unpin, safe Pin::new works without unsafe:
let mut pinned = Pin::new(&mut instance);
// Projecting the struct gives Pin<&mut T> for #[pin] and &mut U for untagged:
let proj = pinned.as_mut().project();
let pinned_ref: Pin<&mut i32> = proj.pinned_data;
assert_eq!(*pinned_ref, 42);
// Unpinned field is a plain &mut String:
*proj.unpinned_data = String::from("updated");
// Untagged PhantomPinned field is a plain &mut PhantomPinned:
let _unpinned_marker: &mut PhantomPinned = proj._pin;
// Drop pinned/proj borrow before accessing instance:
drop(pinned);
assert_eq!(instance.unpinned_data, "updated");
}
#[test]
fn test_tagged_phantom_forces_not_unpin() {
// TaggedPhantom<T> requires PhantomPinned: Unpin for Unpin, making it !Unpin.
// Pinning must be done via Box::pin or pin_mut!, not Pin::new(&mut instance).
let mut boxed = Box::pin(TaggedPhantom {
pinned_data: 42,
_pin: PhantomPinned,
});
let proj = boxed.as_mut().project();
let pinned_ref: Pin<&mut i32> = proj.pinned_data;
assert_eq!(*pinned_ref, 42);
let _pinned_marker: Pin<&mut PhantomPinned> = proj._pin;
}
#[test]
fn test_attribute_not_unpin_suppresses_unpin() {
// #[project(!Unpin)] suppresses auto-Unpin generation
let mut boxed = Box::pin(AttributeNotUnpin {
pinned_data: 99,
});
let proj = boxed.as_mut().project();
let pinned_ref: Pin<&mut i32> = proj.pinned_data;
assert_eq!(*pinned_ref, 99);
}
#[test]
fn test_project_ref_shared_projection() {
let instance = UntaggedPhantom {
pinned_data: 100,
unpinned_data: "hello",
_pin: PhantomPinned,
};
let pinned = Pin::new(&instance);
let proj_ref = pinned.project_ref();
// #[pin] field projects to Pin<&T>
let pinned_data_ref: Pin<&i32> = proj_ref.pinned_data;
assert_eq!(*pinned_data_ref, 100);
// Untagged field projects to &U
let unpinned_ref: &&str = proj_ref.unpinned_data;
assert_eq!(*unpinned_ref, "hello");
}
#[test]
fn test_project_replace_moves_unpinned_by_value() {
let mut target = Replaceable {
pinned_data: 1,
unpinned_data: vec![10, 20, 30],
};
let pinned = Pin::new(&mut target);
let replacement = Replaceable {
pinned_data: 2,
unpinned_data: vec![40, 50],
};
// project_replace moves out owned unpinned fields and replaces struct contents
let old_proj = pinned.project_replace(replacement);
// The unpinned field is returned by value (owned Vec<i32>)
let moved_vec: Vec<i32> = old_proj.unpinned_data;
assert_eq!(moved_vec, vec![10, 20, 30]);
// The target instance now contains replacement values
assert_eq!(target.pinned_data, 2);
assert_eq!(target.unpinned_data, vec![40, 50]);
}
}