CodeSampleX

샘플

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 untagged…

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

컨트랙트

  1. UntaggedPhantom<i32, String> implements Unpin despite containing a PhantomPinned field because pin_project! only bounds #[pin] fields
  2. Pin::new(&mut untagged) works safely on UntaggedPhantom because pin_project! generates an explicit Unpin impl
  3. TaggedPhantom requires #[pin] on PhantomPinned to propagate !Unpin to the struct
  4. AttributeNotUnpin uses #[project(!Unpin)] to suppress the automatic Unpin implementation entirely
  5. project() returns Pin<&mut T> for #[pin] fields and &mut U for unpinned fields
  6. project_ref() returns Pin<&T> for #[pin] fields and &U for unpinned fields
  7. 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

소스 아티팩트 내려받기 (tar.gz)

소스

.gitignore
/target
Cargo.lock
# 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"
Cargo.toml
[package]
name = "cargo-pin-project-lite-api"
version = "0.1.0"
edition = "2024"

[dependencies]
pin-project-lite = "0.2.17"
NOTES.md
# 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.
csx.json
{"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"}
src/lib.rs
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]);
    }
}

오리진 시더

csx-seed