在 Rust 中将一种类型映射到另一种类型

Mapping a type to another type in rust

我正在尝试编写一个 Rust(元)函数,将一些输入类型映射到一些不相关的输出类型。

来自 C++ 背景,我通常会这样写:

template<typename T>
struct f;

template<>
struct f<int> { using type = double; };

using input_type = int;
using output_type = f<input_type>::type;

我天真的尝试用 Rust 编写相同的代码如下所示:

macro_rules! f {
  ($in: ty) => {
    match $in {
      i32 => f32,
    }
  }
}

type OutputType = f!(i32);

但是,好吧,这编译失败,因为宏显然 return 不是一个类型。

$ 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
  |

将一种类型映射到另一种类型的惯用 Rust 方法是什么?

惯用的方式可能是 typetrait 中,例如

trait Matcher {
    type Output;
}

impl Matcher for i32 {
    type Output = f32;
}

但是,如果要使用 macro_rules! 宏,则需要直接在模式上进行匹配。 match 关键字具有误导性,因为您当前的宏扩展为

    match i32 {
      i32 => f32,
    }

但是 match 需要一个表达式,而不是类型。相反,使用模式的类型:

macro_rules! f {
    (i32) => {
        f32
    };
    (i64) => {
        f64
    };
}

type OutputType = f!(i32);

您的宏不起作用的原因是 match。您的代码 type OutputType = f!(i32); 将扩展为:

type OutputType = match i32 {
    i32 => f32,
};

但是你不能 match 类型。 match 仅适用于值。但是,macro_rules! 本身已经具有对标记进行操作的模式匹配功能。所以你可以这样写宏:

macro_rules! f {
  (i32) => { f32 };
  (i64) => { f64 };
}

type OutputType = f!(i32);

但这离你的C++例子还有很远的距离!宏仅对其输入标记进行操作,这意味着这仅适用于 literal 匹配。例如,这行不通:

fn foo<T>() {
    let _: f!(T) = todo!();
}

这会导致“错误:没有规则需要令牌 T”。

要拥有从一种类型到另一种类型的类型级函数,您想在 Rust 中使用特征。例如:

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!();
}

关于使用这样的特征还有很多要说的。整个系统是图灵完备的,人们在其中构建了很多东西。但这应该足以完成此问答。