pattern-wishcast: enum pattern types in 2025 rust

Discussing the pattern types proposal, imitating some of it with uninhabited variants.
by Luna Nova

tl;dr: pattern-wishcast for an approximation of enum pattern types, cargo-derive-doc for a cargo subcommand that documents what macros expand to


wishcasting /ˈwɪʃˌkɑːstɪŋ/
n.-The act of interpreting information or a situation in a way that casts it as favorable or desired, although there is no evidence for such a conclusion; a wishful forecast.

Rust is going to get pattern types. edition = 2027?

Pattern Types§

Pattern types are a proposed name for a subtype of an existing type based on a match-like predicate, from an in-progress RFCpat_rfc by github/joboet. Some examples of pattern types given by the RFC are:

Option<i32> is Some(_)
i32 is 1..16
&[Ordering] is [Ordering::Less, ..]

In the above syntax the pattern after is acts as a predicate constraining which values of the supertype are valid members of the pattern type.
Pattern types are a form of predicate subtypingpr_st; they are limited to predicates that Rust's patterns can express.
Pattern types are described as refinement types in the WIP RFC body, but are less powerful than refinement typesref_st as typically described in the literature.

Pattern types would be the first Rust types to have subtyping other than through lifetime variance or outright equality.

Niche Optimizations§

Rust has some special types like NonZero that allow for niche optimizations.

A value that is known not to equal zero.

This enables some memory layout optimization. For example, Option<NonZero<u32>> is the same size as u32:

use core::{num::NonZero};
assert_eq!(size_of::<Option<NonZero<u32>>>(), size_of::<u32>());

Pattern types allow implementing the same thing in a more general way. NonZero<u32> would be superceded by u32 is 1...

Layout optimizations for pattern types do not directly impact the size of the pattern type; instead they impact the layout of types that contain the pattern type by allowing them to reuse bit patterns that would represent values that do not match the pattern's predicate.

A u32 is 1..10 has many bit patterns that are invalid such as 0, so an Option<u32 is 1..10> can pick one of those bit patterns to represent None; allowing this to hold: assert_eq!(size_of::<Option<u32 is 1..10>>(), size_of::<u32>());

This layout optimization does not cause a problem for subtypingrust_pat_pr_wip: a u32 is 1..10 ≤: u32, an Option<u32 is 1..10> is not a subtype of u32. Some of the possible bit patterns in an Option<u32 is 1..10>'s backing u32 sized storage must not be interpreted as u32.
We can not get a u32 from a Option<u32 is 1..10>::None's backing storage without an unsafe transmute. If it's Some we can get a &(u32 is 1..10) out of it, and that being a subtype of u32 is safe. We don't have to worry about something else mutating it to None and making it a u32 while we have that reference due to rust's usual mutability guarantees.

It's critical this doesn't hold for mutable references. An &mut(u32 is 1..10) must not be usable as an &mut u32, as we could store 0.

Implementation Status§

rust has an unstable implementationrust_pat_tracking of pattern types which relies on a macro, sidestepping a lack of decision on syntax. The implementation was championed by github/oli-obk.

#![feature(pattern_types)
type NonZeroU32 = std::pat::pattern_type!(u32 is 1..);

As of 2025-06:

  • numeric ranges work
  • layout optimization works, similar to NonZero<_>
  • enum variants do not work
  • subtyping does not work :c
    • there's a very limited form of coercion for numeric literals only. let x: pattern_type!(u32 is 5..10) = 7; works

from future import rust::rfcs::enum_pattern_types§

Can we create pattern types that allow specific enum variants in current stable rust? No.
Can we emulate something like pattern types if we plan ahead when defining an enum? Maybe!

Let's try to imitate this type alias:

// A result that can only be the Ok variant
type OkResult<O, E> = Result<O, E> is Ok(_);
// SPDX-SnippetBegin
// SPDX-License-Identifier: MIT OR CC0-1.0
#![feature(never_type)]
#[repr(C)]
enum PatternableResult<P: ResultVariantPresence, O, E> {
  // 2nd arg is either () or !.
  // If it's ! it's uninhabited so this variant can't be constructed and doesn't need to be matched
  Ok(O, P::Ok),
  Err(E, P::Err),
}
type AnyResult<O, E> = PatternableResult<AnyResultVariantPresence, O, E>;
type OkResult<O, E> = PatternableResult<OkResultVariantPresence, O, E>;

// AnyResult instances can be either variant, OkResult instances can only be Ok

trait ResultVariantPresence {
  type Ok;
  type Err;
}
struct OkResultVariantPresence;
impl ResultVariantPresence for OkResultVariantPresence {
  type Ok = ();
  type Err = !;
}
struct AnyResultVariantPresence;
impl ResultVariantPresence for AnyResultVariantPresence {
  type Ok = ();
  type Err = ();
}

fn unwrap_safely(ok: OkResult<i64, ()>) -> i64 {
  match ok {
    OkResult::Ok(contains, _) => {
      // Matched on the only possible variant Ok of OkResult
      contains
    } // We don't need another match arm, rustc can tell Err is uninhabited
  }
}
// SPDX-SnippetEnd

It works! So verbose, and we haven't exposed a safe way to turn an AnyResult that's Ok into an OkResult while proving it is valid to do so.

Let's write a macro which can automate the traits and the conversion boilerplate.

pattern-wishcast§

crates.io docs.rs GitHub

⚠️ pattern-wishcast is a work in progress. Expect breaking changes. miri seems happy with its transmutes, but I couldn't find any reference to point to and confidently declare its internals are ok
If pattern-wishcast on crates.io is > version 0.1 this post is likely using an outdated API

// SPDX-SnippetBegin
// SPDX-License-Identifier: MIT OR CC0-1.0
pattern_wishcast! {
  enum StuckEvaluation = {
    Var { id: usize },
    Application { func: Box<FlexValue>, arg: Box<FlexValue> }
  };

  enum Value is <P: PatternFields> = {
    HostValue { value: String },
    // Self -> Value<P>, recursively preserves applied pattern
    // including checking before conversion in generated downcast
    TupleValue { elements: Vec<Self> },
    StuckEvaluation,
  };

  // with real pattern types we wouldn't need to explicitly make an alias with a wildcard
  type FlexValue = Value is _; 
  type StrictValue = Value is HostValue { .. } | TupleValue { .. };

  // No real subtyping but we can pretend by generating upcast and try downcast impls
  // With real pattern types in rustc no need to declare anything like this
  // StrictValue would be a subtype of FlexValue just from specifying predicates that imply that relation
  #[derive(SubtypingRelation(upcast=to_flex, downcast=try_to_strict))]
  impl StrictValue : FlexValue;
}
// <generated by cargo-derive-doc>
// Macro expansions:
//   pub enum StuckEvaluation
//   pub enum Value<P : PatternFields>
//   pub struct ValueType
//   impl  PatternFields for ValueType
//   pub struct FlexValueType
//   impl  PatternFields for FlexValueType
//   pub struct StrictValueType
//   impl  PatternFields for StrictValueType
//   pub type FlexValue = Value <FlexValueType>
//   pub type StrictValue = Value <StrictValueType>
//   impl  StrictValue
//   impl  FlexValue
// </generated by cargo-derive-doc>
// SPDX-SnippetEnd

pattern_wishcast! lets us pretend we have pattern types for enum variants in 2025 stable rust by:

  • generating traits with associated types that are either ! or ()
    • which are used to make variants uninhabited
    • we can use enum Never {} rather than ! to avoid depending on an unstable feature
  • generating upcast and downcast methods along with ref/mut ref variants where safe
  • generating some tests that hopefully fail if future rust makes changes that break the layout assumptions

I'm not confident the generated downcasts and upcasts are sound.
miri seems happy with transmutes between types that differ only in an unused enum variant swapping from ! to () or vice versa, but I couldn't find any reference to point to and confidently declare it's ok.
If you find a safety issue please open an issue.

See pattern-wishcast/examples for a larger demo.

Recursive Patterns§

pattern-wishcast allows recursively applying a pattern with Self. In the above example, a FlexValue::TupleValue's elements are FlexValue, and a StrictValue::TupleValue's elements are StrictValue.

I don't think it's possible to achieve this in the pattern types RFCpat_rfc. It would be great to extend the proposal to allow for this.

Some ideas for extensions to the rfc:

// Allow recursion in a type alias definition with a predicate?
type StrictValue = Value is HostValue { .. } | TupleValue { elements: Vec<StrictValue> };
// Special Self?
type StrictValue = Value is HostValue { .. } | TupleValue { elements: Vec<Self> };

Maybe it's possible with sufficient abuse of the type system (remember trait TypeFn {type Output<A>;} struct Fix<F: TypeFn>(<F as TypeFn>::Output<Fix<F>>);?). I haven't worked it out yet and don't have a pattern-type supporting compiler to play with.

cargo-derive-doc§

crates.io GitHub

While working on pattern-wishcast I realized it was hard to tell what a macro does when reviewing diffs and decided to make cargo-derive-doc.

cargo-derive-doc is a cargo subcommand that maintains comments indicating what types and impls are added by a macro invocation. It's useful anywhere you don't have a functioning IDE with macro expansion, whether that's github's review UI, a minimal editor in a terminal, an LLM code assistant or because rust-analyzer broke and can't expand macros again.

It's almost always because rust-analyzer broke again.
Why did I try to implement it on top of rust-analyzer's ra_ap_hir_expand crate…

Anyway, cargo-derive-doc is workable now. It'll probably mess up if you run it on minified code but otherwise should be good enough. It's relying on diffing expanded and original code to map where new items originated since I couldn't get ra_ap_hir_expand working.

Check the README for usage instructions.


pr_st

Predicate Subtyping was described in Subtypes for Specifications: Predicate Subtyping in PVS 1998

ref_st

Refinement types were introduced in Refinement Types for ML 1991.

rust_pat_pr_wip

See discussion on github/rust-lang Pattern types MVP #107606



Cite as BibTeX
@misc{pattern-wishcast,
    author = {Luna Nova},
    title = {pattern-wishcast: enum pattern types in 2025 rust},
    year = {2025},
    url = {https://lunnova.dev/articles/pattern-wishcast/},
    howpublished = {https://lunnova.dev/articles/pattern-wishcast/},
    urldate = {2025-07-06},
    note = {lunnova.dev - Discussing the pattern types proposal, imitating some of it with uninhabited variants.}
}

tagged