【问题标题】:Is there a way to get the type of a variable in a macro?有没有办法在宏中获取变量的类型?
【发布时间】:2021-12-18 18:13:00
【问题描述】:

我有一个程序属性宏,它给定一个函数对每个二进制表达式进行操作,例如let a = b + c; 并根据它返回另一个表达式。对于+操作,根据类型需要知道abc的类型。

有没有办法在编译时获取推断的变量类型?

(就像 rust-analyser 可能会显示推断类型,我可以在宏中获取这些类型吗?)

快速示例 - Playground

为了在 Rust 操场上更简洁地说明我的方法,我们可以使用声明性宏,它在给定变量上调用函数,该函数的细节基于给定变量的类型。

我可以在 Rust 操场上最接近我想要的功能:

macro_rules! SomeMacro {
    ($x:expr) => {{
        $x.some_function(3.)
    }};
}
trait SomeTrait {
    fn some_function(&self,x:f32) -> f32;
}
impl SomeTrait for u32 {
    fn some_function(&self,x:f32) -> f32 {
        x * 3.
    }
}

fn main() {
    let a = 3u32;
    let b = SomeMacro!(a);
    assert_eq!(b,9.);
}

我怎样才能使事情像这样工作:

fn some_function<u32>(x:f32) -> f32 {
    3. * x
}
fn some_function<u32,i8,f32>(x:f32) -> f32 {
    3. * x
}
fn main() {
    let a = 3u32;
    let b = <type_of<a>()>::some_function(3.);
    assert_eq!(b,9.);
    let c = 5i8;
    let d = <type_of<a>(),type_of<b>(),type_of<c>()>::some_function(2.);
    assert_eq!(c,6.);
}

综合示例 - .zip

lib.rs
extern crate proc_macro;
use proc_macro::TokenStream;

#[proc_macro_attribute]
pub fn my_attribute_macro(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let ast = syn::parse_macro_input!(item as syn::Item);
    // eprintln!("{:#?}",ast);

    // Checks item is function.
    let mut function = match ast {
        syn::Item::Fn(func) => func,
        _ => panic!("Only `fn` items are supported."),
    };
    let block = &mut function.block;

    // Updates statements
    let statements = block.stmts
        .iter()
        .filter_map(|statement| update_statements(statement))
        .collect::<Vec<_>>();
    block.stmts = statements;

    let new = quote::quote! { #function };
    TokenStream::from(new)
}
fn update_statements(stmt: &syn::Stmt) -> Option<syn::Stmt> {
    let local = match stmt {
        syn::Stmt::Local(local) => local,
        _ => return Some(stmt.clone())
    };
    let init = &local.init;
    let bin_expr = match *init.as_ref().unwrap().1 {
        syn::Expr::Binary(ref bin) => bin,
        _ => return Some(stmt.clone())
    };

    eprintln!("looking at: {:#?}",stmt);
    // 
    None
}
main.rs
use macro_test::*;
// Goals: 
// - Map from `x` being equal to `a+b` to `x` being equal to `a*b` based off `x` being `f32`.
// - Map from `y` being equal to `c+d` to `y` being equal to `c/d` based off `y` being `u32`.
#[my_attribute_macro]
fn my_function(a: f32, b: f32, c: u32, d: u32) {
    let x = a + b;
    let y = c + d;
}

fn main() {
}

其中一张照片看起来像(来自cargo expand --bin macro-test):

looking at: Local(
    Local {
        attrs: [],
        let_token: Let,
        pat: Ident(
            PatIdent {
                attrs: [],
                by_ref: None,
                mutability: None,
                ident: Ident {
                    ident: "y",
                    span: #0 bytes(316..317),
                },
                subpat: None,
            },
        ),
        init: Some(
            (
                Eq,
                Binary(
                    ExprBinary {
                        attrs: [],
                        left: Path(
                            ExprPath {
                                attrs: [],
                                qself: None,
                                path: Path {
                                    leading_colon: None,
                                    segments: [
                                        PathSegment {
                                            ident: Ident {
                                                ident: "c",
                                                span: #0 bytes(320..321),
                                            },
                                            arguments: None,
                                        },
                                    ],
                                },
                            },
                        ),
                        op: Add(
                            Add,
                        ),
                        right: Path(
                            ExprPath {
                                attrs: [],
                                qself: None,
                                path: Path {
                                    leading_colon: None,
                                    segments: [
                                        PathSegment {
                                            ident: Ident {
                                                ident: "d",
                                                span: #0 bytes(324..325),
                                            },
                                            arguments: None,
                                        },
                                    ],
                                },
                            },
                        ),
                    },
                ),
            ),
        ),
        semi_token: Semi,
    },
)

【问题讨论】:

  • 我不想玩刻板印象,但你想......因为类型是u32而隐式地把加法变成除法?这似乎是一个混乱的秘诀。
  • @GManNickG 仅举个例子,实际上它会将语句转换为它们的派生词。但这会给这个例子增加不必要的代码。

标签: rust rust-macros


【解决方案1】:

加法

OP 更新了他们的帖子,澄清了他们的问题,此次添加是为了回应这一点。

看来您可能想要实现新的类型模式。在您的评论中,您提到您希望更改标准运算符(+-*/,...)在使用特定过程宏修饰的函数上的行为。

newtype 模式如下所示:

struct Differentiable(f32);

注意:我们甚至可以在这里使用泛型,这样我们的新类型就可以超过f32u32u8 ...

我们现在可以在我们的新类型之上实现我们想要的操作符。

impl Add for Differentiable {
    type Output = Self;

    fn add(self, other: Self) -> Self {
        Self(self.0 * other.0)
    }
}

这给了我们行为:

fn main() {
  let x = Differentiable(5.0);
  let y = Differentiable(2.0);
  assert_eq!(x + y, 10);
}

此外,我们可以在该类型上实现IntoFrom 以使其使用更符合人体工程学。我们也可以通过实现std::ops中的traits来实现其他操作符。

现在,您提到希望将其实现为函数上的过程宏。我会强烈建议不这样做。我认为这样的功能会:

  • 增加混淆的可能性
  • 增加库的复杂性
  • 直接违反标准防锈习惯
  • 影响 IDE 使用率
  • 增加编译时间

见:

就我个人而言,我看不到提供这样一个宏的价值,也看不出它如何以“感觉”正确的方式实现。不过还是有可能的:

简单地从这个 ast 转换:

fn my_function(a: f32, b: f32, c: u32, d: u32) {
  let x = a + b;
  let y = c + d;
}

到:

fn my_function(a: f32, b: f32, c: u32, d: u32) {
  fn inner(
    a: Differentiable,
    b: Differentiable,
    c: Differentiable,
    d: Differentiable,
  ) {
    let x = a + b;
    let y = c + d;
  }

  inner(
    Differentiable(a),
    Differentiable(b),
    Differentiable(c),
    Differentiable(d),
  )
}

进一步阅读:


之前的回复

我不确定知道宏内的表达式类型对您有什么用。 Rust 的类型推断系统应该足够强大,可以处理大多数类型解析的情况。

例如,我会使用泛型和特征边界来实现上面的 sn-p:

use std::ops::Add;

fn add<A: Into<B>, B: Add>(a: A, b: B) -> <B as Add>::Output {
    a.into() + b
}

fn main() {
    let a: u8 = 4;
    let b: f32 = 3.5;
    assert_eq!(add(a, b), 7.5);
}

Rust 会自动为 AB 找到正确的值。如果由于我们的 trait 限制它不能,rustc 将输出有用的错误消息:

fn main() {
    let a = "Hello, world!";
    let b = 56;
    add(a, b);
}

结果:

error[E0277]: the trait bound `{integer}: From<&str>` is not satisfied
  --> src/main.rs:10:12
   |
10 |     add(a, b);
   |     ---    ^ the trait `From<&str>` is not implemented for `{integer}`
   |     |
   |     required by a bound introduced by this call
   |
   = help: the following implementations were found:
             <Arc<B> as From<Cow<'a, B>>>
             <Arc<CStr> as From<&CStr>>
             <Arc<CStr> as From<CString>>
             <Arc<OsStr> as From<&OsStr>>
           and 329 others
   = note: required because of the requirements on the impl of `Into<{integer}>` for `&str`
note: required by a bound in `add`
  --> src/main.rs:3:11
   |
3  | fn add<A: Into<B>, B: Add>(a: A, b: B) -> <B as Add>::Output {
   |           ^^^^^^^ required by this bound in `add`

For more information about this error, try `rustc --explain E0277`.

这里 rust 无法解析 a 的类型以适应我们在 add 函数中的限制。

现在这通过宏工作,无需额外工作:

macro_rules! some_macro {
  ($x:expr) => (add($x, 3.0))
}

fn main() {
    let x = 42u32;
    let y = some_macro!(x);
    assert_eq!(y, 45.0);
}

进一步阅读:

【讨论】:

  • 添加了一个更明确的更长示例,应该强调为什么这不起作用,我需要我所询问的内容。
  • 我已经相应地更新了我的回复。
  • 我要了一些东西,我想在我的例子中使用这个。你的回答都不是我要求的。我想在编译时知道值类型。
  • 不,这对于宏是不可能的。在运行宏时,类型尚未完全解析。您的宏也不能自己得出该信息。这就是为什么我提供一个试图证明宏不是必需的答案的原因。 rust 类型系统完全能够在没有宏的情况下提供有问题的行为。抱歉,我应该把这个推理放在我的回答中。
【解决方案2】:

在处理完宏之前,编译器不会决定类型。在许多情况下,宏可以更改推断的类型。这就是为什么可以这样写:

let mut xs = vec![];
xs.push(1);

类型推断可以返回并将正确的类型分配给xs。如果vec! 必须在扩展之前知道类型,这将是不可能的。您将需要以另一种方式解决此问题。请参阅@PossiblyAShrub 对一种方法的回答。

The Rust Programming Language:Macros 注释(已添加重点):

此外,宏在编译器解释代码含义之前被扩展,因此宏可以例如实现给定类型的特征。

您可能想探索论坛中的Native Differential Programming Support for Rust 讨论,尤其是链接的后续线程。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-25
    • 1970-01-01
    • 2019-03-29
    • 1970-01-01
    • 2020-11-30
    • 2020-02-17
    • 2016-02-21
    相关资源
    最近更新 更多