有没有办法获取宏中变量的类型?

Jon*_*ght 6 rust rust-macros

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

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

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

简单示例 -游乐场

为了在 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.);
}
Run Code Online (Sandbox Code Playgroud)

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

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.);
}
Run Code Online (Sandbox Code Playgroud)

综合示例 - .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
}
Run Code Online (Sandbox Code Playgroud)
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() {
}
Run Code Online (Sandbox Code Playgroud)

其中一张照片看起来像(来自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,
    },
)
Run Code Online (Sandbox Code Playgroud)

Rob*_*ier 5

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

let mut xs = vec![];
xs.push(1);
Run Code Online (Sandbox Code Playgroud)

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

正如《Rust 编程语言:宏》所指出的(强调是添加的):

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

您可能想从论坛中探索Rust 的本机差分编程支持讨论,特别是链接的后续线程。


Pos*_*rub 2

添加

OP 已更新他们的帖子澄清他们的问题,此添加是对此的回应。

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

新类型模式如下所示:

struct Differentiable(f32);
Run Code Online (Sandbox Code Playgroud)

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

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

impl Add for Differentiable {
    type Output = Self;

    fn add(self, other: Self) -> Self {
        Self(self.0 * other.0)
    }
}
Run Code Online (Sandbox Code Playgroud)

这给了我们以下行为:

fn main() {
  let x = Differentiable(5.0);
  let y = Differentiable(2.0);
  assert_eq!(x + y, 10);
}
Run Code Online (Sandbox Code Playgroud)

此外,我们可以在类型上实施Into和 ,From使其使用起来更符合人体工程学。我们还可以通过实现 中的特征来实现其他运算符std::ops

现在,您提到想要将其实现为函数上的过程宏。我强烈建议否则。我认为这样的功能将:

  • 增加混淆的可能性
  • 增加库的复杂性
  • 直接违反 Rust idoms 标准
  • 影响 IDE 使用
  • 增加编译时间

看:

就我个人而言,我不认为提供这样一个宏有什么价值,也不知道如何以“感觉”正确的方式实现它。尽管如此,这是可能的:

只需从此 ast 进行转换:

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

到:

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),
  )
}
Run Code Online (Sandbox Code Playgroud)

进一步阅读:


之前的回复

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

例如,我将使用泛型和特征边界来实现上面的代码片段:

fn main() {
  let x = Differentiable(5.0);
  let y = Differentiable(2.0);
  assert_eq!(x + y, 10);
}
Run Code Online (Sandbox Code Playgroud)

ARust 会自动找到和的正确值B。如果不能,由于我们的特征限制,rustc 将输出有用的错误消息:

fn main() {
    let a = "Hello, world!";
    let b = 56;
    add(a, b);
}
Run Code Online (Sandbox Code Playgroud)

结果是:

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`.
Run Code Online (Sandbox Code Playgroud)

这里 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);
}
Run Code Online (Sandbox Code Playgroud)

进一步阅读:

  • 不,这对于宏来说是不可能的。运行宏时,类型尚未完全解析。您的宏也无法自行导出该信息。这就是为什么我提供一个答案,试图证明宏不是必需的。Rust 类型系统完全能够在没有宏的情况下提供有问题的行为。抱歉,我应该把这个推理放在我的回答中。 (3认同)