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 unsubmitted RFC by joboet1. 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 subtyping2; 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 types3 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.
from future import rust::rfcs::enum_pattern_types
Can we create pattern types in current rust? No.
Can we emulate something like pattern types if we plan ahead when defining an enum? Yes!
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(_);
#![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
}
}
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
⚠️ 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
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>
pattern_wishcast!
lets us badly pretend we have pattern types in 2025 rust by:
- generating traits with associated types that are either ! or ()
- which are used to make variants uninhabited
- generating upcast and downcast methods along with ref/mut ref variants
- 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 RFC1. 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
While working on pattern_wishcast
I noticed it would be useful to automatically document result of a macro expansion in terms of visible items.
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.
Predicate Subtyping was described in Subtypes for Specifications: Predicate Subtyping in PVS 1998
Refinement types were introduced in Refinement Types for ML 1991.