3 releases
0.1.2 | Apr 26, 2023 |
---|---|
0.1.1 | Jan 19, 2020 |
0.1.0 | Jan 12, 2020 |
#148 in Procedural macros
19,670 downloads per month
Used in 2 crates
(via autometrics)
17KB
162 lines
spez
Macro to specialize on the type of an expression.
This crate implements auto(de)ref specialization: A trick to do specialization in non-generic contexts on stable Rust.
For the details of this technique, see:
- Autoref-based stable specialization by David Tolnay
- Generalized Autoref-Based Specialization by Lukas Kalbertodt
What it can and cannot do
The auto(de)ref technique—and therefore this macro—is useless in generic functions, as Rust resolves the specialization based on the bounds defined on the generic context, not based on the actual type when instantiated. (See the example below for a demonstration of this.)
In non-generic contexts, it's also mostly useless, as you probably already know the exact type of all variables.
The only place where using this can make sense is in the implementation of
macros that need to have different behaviour depending on the type of a
value passed to it. For example, a macro that prints the Debug
output of
a value, but falls back to a default when it doesn't implement Debug
.
(See the example below for a demonstration of
that.)
How to use it
The basic syntax of the macro is:
spez! {
for <expression>;
match <type> { <body> }
[match <type> { <body> }]
[...]
}
The examples below show more details.
Simple specialization
In the most simple case, you use this macro to match specific types:
let x = 0;
spez! {
for x;
match i32 {
println!("x is a 32-bit integer!");
}
match &str {
println!("x is a string slice!");
assert!(false);
}
}
Return types
Values can be returned from the matches, but have to be explicitly
specified for each match
. They do not have to be the same for every
match
.
let x = 0;
let result = spez! {
for x;
match i32 -> &'static str {
"x is a 32-bit integer!"
}
match &str -> i32 {
123
}
};
assert_eq!(result, "x is a 32-bit integer!");
Generic matches
Generic matches are also possible. Generic variables can be defined
on the match
, and a where
clause can be added after the type.
The matches are tried in order. The first matches get priority over later ones, even if later ones are perfect matches.
let x = 123i32;
let result = spez! {
for x;
match<T> T where i8: From<T> -> i32 {
0
}
match<T: std::fmt::Debug> T -> i32 {
1
}
match i32 -> i32 {
2
}
};
assert_eq!(result, 1);
Consuming the input
The input (after the for
) is consumed and made available to the match
bodies.
(If you don't want to consume the input, take a reference and also prepend
a &
to the types you're matching.)
let x = Box::new(123);
let result = spez! {
for x;
match<T: Deref<Target = i32>> T -> i32 {
*x
}
match i32 -> i32 {
x
}
};
assert_eq!(result, 123);
Expressions as input
Not just variable names, but full expressions can be given as input.
However, if you want to refer to them from the match bodies, you need to
prepend name =
to give the input a name.
let result = spez! {
for 1 + 1;
match i32 -> i32 { 0 }
match i64 -> i32 { 1 }
};
assert_eq!(result, 0);
let result = spez! {
for x = 1 + 1;
match i32 -> i32 { x }
match i64 -> i32 { 1 }
};
assert_eq!(result, 2);
Capturing variables
Unfortunately, you can't refer to variables of the scope around the spez! {}
macro:
let a = 1;
let result = spez! {
for x = 1;
match i32 {
println!("{}", a); // ERROR
}
};
In a generic function
As mentioned above, the macro is of not much use in generic context, as the specialization is resolved based on the bounds rather than on the actual type in the instantiation of the generic function:
fn f<T: Debug>(v: T) -> &'static str {
spez! {
for v;
match i32 -> &'static str {
":)"
}
match<T: Debug> T -> &'static str {
":("
}
match<T> T -> &'static str {
":(("
}
}
}
assert_eq!(f(0i32), ":(");
In a macro
This is a demonstration of a macro that prints the Debug
output of a
value, but falls back to "<object of type ...>"
if it doesn't implement
Debug
.
macro_rules! debug {
($e:expr) => {
spez! {
for x = $e;
match<T: Debug> T {
println!("{:?}", x);
}
match<T> T {
println!("<object of type {}>", std::any::type_name::<T>());
}
}
}
}
debug!(123);
debug!(NoDebugType);
Dependencies
~285–740KB
~18K SLoC