I'm trying to write a rust (meta-)function that maps some input type to some unrelated output type.
Coming from a C++ background, I would usually write it like this:
template<typename T>
struct f;
template<>
struct f<int> { using type = double; };
using input_type = int;
using output_type = f<input_type>::type;
My naive attempt to write the same in rust looks like this:
macro_rules! f {
($in: ty) => {
match $in {
i32 => f32,
}
}
}
type OutputType = f!(i32);
but, well, this fails to compile because the macro apparently doesn't return a type.
$ rustc typedef.rs
error: expected type, found keyword `match`
--> typedef.rs:3:5
|
3 | match $in {
| ^^^^^ expected type
...
9 | type OutputType = f!(i32);
| -------
| |
| this macro call doesn't expand to a type
| in this macro invocation
|
What's the idiomatic rust way to map one type to another?
The reason your macro doesn't work is because of match
. Your code type OutputType = f!(i32);
will expand to:
type OutputType = match i32 {
i32 => f32,
};
But you can't match
over types. match
only works for values. However, macro_rules!
itself already has a pattern matching feature that operates on tokens. So you could write the macro like this:
macro_rules! f {
(i32) => { f32 };
(i64) => { f64 };
}
type OutputType = f!(i32);
But this is still very far away from your C++ example! A macro just operates on its input tokens meaning that this only works with a literal match. For example, this just doesn't work:
fn foo<T>() {
let _: f!(T) = todo!();
}
This results in "error: no rules expected the token T
".
To have a type-level function from one type to another, you want to use traits in Rust. For example:
trait F {
type Out;
}
impl F for i32 {
type Out = f32;
}
impl F for i64 {
type Out = f64;
}
type OutputType = <i32 as F>::Out;
fn foo<T>() {
// This works now.
let _: <T as F>::Out = todo!();
}
There is a lot more to say about using traits like this. The whole system is Turing complete and people built lots of stuff in it. But this should suffice for this Q&A.