Можно ли определить, является ли поле определенного типа или реализует определенный метод в процедурном макросе?

Я создал процедурный макрос, который реализует черту, но чтобы это работало, мне нужно получить необработанные байты для каждого поля. Проблема в том, как получить байты поля отличается в зависимости от типа поля.

Есть ли способ проверить, существует ли функция в поле и не пытается ли она использовать другую функцию?

Например, что-то вроде этого:

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

В настоящее время я смотрю на создание другой функции trait / member, которую мне просто нужно создать для всех примитивов и создать процедурный макрос для больших полей, таких как структуры. Например:

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

Со строкой, он имеет as_bytes функция-член, в то время как i32 не. Это означает, что мне нужен дополнительный код, когда поле члена структуры не является строкой. Мне может понадобиться match а не if, но if хватит для примера.

1 ответ

Решение

Можно ли определить, является ли поле определенного типа или реализует определенный метод в процедурном макросе?

Нет.

Макросы работают с абстрактным синтаксическим деревом (AST) кода Rust. Это означает, что вы в основном просто получаете символы, которые набрал пользователь.

Если код пользователя имеет что-то вроде type Foo = Option<Result<i32, MyError>> и вы обрабатываете некоторый код, который использует Foo макрос не будет знать, что это "действительно" Option,

Даже если бы он знал тип, знать, какие методы доступны, было бы еще сложнее. Будущие ящики могут создавать черты, которые добавляют методы к существующим типам. В тот момент, когда выполняется процедурный макрос, эти ящики, возможно, еще даже не были скомпилированы.

Я смотрю на создание другой функции trait / member, которую мне просто нужно создать для всех примитивов и создать процедурный макрос для больших полей, таких как структуры.

Это правильное решение. Если вы посмотрите на любой хорошо используемый процедурный макрос, это именно то, что он делает. Это позволяет компилятору делать то, для чего он предназначен.

Это также намного лучше для удобства обслуживания - теперь эти примитивные реализации живут в стандартном файле Rust, а не встроены в макрос. Гораздо проще читать и отлаживать.

Ваш ящик будет иметь что-то вроде этого:

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

И ваш процедурный макрос будет реализовывать это простым способом:

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

становится

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

В качестве конкретного примера, Serde делает то же самое, используя несколько способов сериализации в двоичные данные и из них:

Другие вопросы по тегам