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…

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