Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mapping a type to another type in rust

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?

like image 683
Benno Avatar asked Aug 31 '25 04:08

Benno


1 Answers

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.

like image 131
Lukas Kalbertodt Avatar answered Sep 03 '25 16:09

Lukas Kalbertodt