rust-variance

Diagnose and fix Rust lifetime coercion failures caused by variance and subtyping rules.

2|1|Updated Aug 19, 2026
One-click install
npx skills add https://github.com/po4yka/rust-skills --skill rust-variance-po4yka
Or copy as Structured Prompt for Agent▼
Please help me install this Agent Skill.
Skill: rust-variance
Source: https://github.com/po4yka/rust-skills/tree/main/skills/rust-variance
Command: npx skills add https://github.com/po4yka/rust-skills --skill rust-variance-po4yka

SYSTEM DOCUMENTATION & REQUIREMENTS

💡 This Skill includes references (resource) components.

What problem does it solve? Rust developers hit confusing compiler errors like "is invariant over the parameter", "borrowed for 'static", or "lifetime may not live long enough" when a lifetime coercion is refused. This Skill explains why the type constructor—not the lifetime—decides variance, and provides compile probes, exact rustc diagnostics, and worked repairs for each failure shape. ## Core Features & Use Cases - Variance probes and tables: Compile three one-line probe functions to determine whether a type is covariant, contravariant, or invariant, with a full variance table for std constructors and PhantomData forms. - Symptom-to-fix routing: A triage table maps exact rustc notes (E0597, E0106, invariance notes) to the right repair, including trait bounds matching by equality, free lifetimes in dyn Trait, and &mut parameters pinning caller lifetimes. - Soundness and API guidance: Covers unbounded lifetimes from raw pointers, the unsound contravariant channel pattern, and how adding Cell/RefCell/Mutex to a public type is a breaking change to lock with compile_fail doctests. - Use Case: When names.resize_with(10, service_name) fails with E0597, use this Skill to learn that fn item types fix Output by equality and apply one of the three fixes: an unbounded output lifetime, a wrapping closure, or a fn pointer coercion. ## Quick Start Ask the agent to diagnose why the compiler says my struct is invariant over a lifetime parameter and show me how to fix the coercion.

Frequently Asked Questions about rust-variance

High-intent search queries and answers about installing and using this skill.

FAQPage Schema
How do I check if a Rust type is covariant, contravariant, or invariant?▼

Compile three one-line probe functions that attempt to shorten and lengthen a lifetime through the type constructor, then read which ones compile. If only the shortening direction compiles the type is covariant, only the lengthening direction means contravariant, and rejection of both means invariant.

Why does Rust say my struct is invariant over the parameter?▼

A struct becomes invariant when a field uses the parameter both covariantly and contravariantly, or contains interior mutability like Cell, RefCell, Mutex, or UnsafeCell. Mutable references and raw mutable pointers also force invariance in their type parameter.

How to fix E0597 does not live long enough at resize_with?▼

The fn item type fixes its Output to &'static str, and trait bounds match by equality, so no coercion applies. Give the producer an unbounded output lifetime, wrap the call in a closure, or coerce it to a fn pointer which is covariant in the return type.

Is adding Cell or RefCell to a public Rust struct a breaking change?▼

Yes, wrapping a field in Cell, RefCell, Mutex, or any UnsafeCell flips the struct from covariant to invariant, breaking downstream code that shortened lifetimes. Lock the intended variance with a private probe function or a compile_fail doctest run via cargo test --doc.

Why is std mpsc Sender invariant in its message type?▼

Sender is invariant because the channel stores and drops values of the message type, so allowing contravariance would let short-lived messages escape into a 'static context and be dropped after their borrows end. Write consumers as Sender<Message<'_>> and coerce at the send site instead.

When is PhantomData<fn(T)> unsound in Rust?▼

PhantomData<fn(T)> makes a type contravariant in T, which is sound only for a type that never stores or drops a T. On a queue or channel handle that owns values, it is a soundness bug that Miri reports as a use-after-free; use PhantomData<T> instead.