首页 > 解决方案 > 是否可以判断一个字段是某种类型还是在过程宏中实现某种方法?

问题描述

我创建了一个实现特征的过程宏,但为了使其工作,我需要获取每个字段的原始字节。问题是如何获取字段的字节取决于字段的类型。

是否有某种方法可以测试一个函数是否存在于一个字段中并且它是否不尝试另一个函数?

例如这样的:

if item.field::function_exist {
    //do code
} else {
    //do other code
}

目前,我正在考虑创建另一个特征/成员函数,我只需要为所有原语创建它,并为更大的字段(例如结构)创建一个过程宏。例如:

if item.field::as_bytes().exists {
    (&self.#index).as_bytes()
} else {
    let bytes = (&self.#index).to_bytes();
    &bytes
}

对于字符串,它有一个as_bytes成员函数,而i32没有。这意味着当结构的成员字段不是字符串时,我需要额外的代码。我可能需要 amatch而不是 a if,但是if对于这个例子来说就足够了。

标签: rustrust-proc-macros

解决方案


Is it possible to tell if a field is a certain type or implements a certain method in a procedural macro?

No, it is not.

Macros operate on the abstract syntax tree (AST) of the Rust code. This means that you basically just get the characters that the user typed in.

If user code has something like type Foo = Option<Result<i32, MyError>>, and you process some code that uses Foo, the macro will not know that it's "really" an Option.

Even if it did know the type, knowing what methods are available would be even harder. Future crates can create traits which add methods to existing types. At the point in time that the procedural macro is running, these crates may not have even been compiled yet.

I am looking at creating another trait/member function that I just have to create for all primitives and create a procedural macro for larger fields such as structs.

This is the correct solution. If you look at any existing well-used procedural macro, that's exactly what it does. This allows the compiler to do what the compiler is intended to do.

This is also way better for maintainability — now these primitive implementations live in a standard Rust file, as opposed to embedded inside of a macro. Much easier to read and debug.

Your crate will have something like this:

// No real design put into this trait
trait ToBytes {
    fn encode(&self, buf: &mut Vec<u8>);
}

impl ToBytes for str {
    fn encode(&self, buf: &mut Vec<u8>) {
        buf.extend(self.as_bytes())
    }
}

// Other base implementations

And your procedural macro will implement this in the straightforward way:

#[derive(ToBytes)]
struct Foo {
    a: A,
    b: B,
}

becomes

impl ToBytes for Foo {
    fn encode(&self, buf: &mut Vec<u8>) {
        ToBytes::encode(&self.a, buf);
        ToBytes::encode(&self.b, buf);
    }
}

As a concrete example, Serde does the same thing, with multiple ways of serializing to and from binary data:


推荐阅读