#rust #rust-macros #rust-proc-macros
#Ржавчина #rust-макросы #rust-proc-macros
Вопрос:
Я пишу производный процедурный макрос, в котором все значения преобразуются в Options
. Проблема в том, что любые Option
поля в структуре могут содержаться внутри этих Option
типов. Сам по себе это не представляет большой проблемы, пока я не начну сериализовывать данные с помощью serde. Я хочу иметь возможность пропускать любые значения, где значение равно None
, но бывают случаи, когда это будет что-то вроде Some(None)
или Some(CustomOption::None)
. Оба эти случая не более значимы, чем простой None
, но я не могу просто писать #[serde(skip_serializing_if = "Option::is_none")]
в производных полях. Конечно, они выводят null
значение в формате JSON.
В принципе, я хочу иметь возможность использовать библиотеку syn, чтобы проверить, будет ли тип внутреннего значения производного поля Option
и сгладить его в единственное число Option<T>
в производной структуре вместо Option<Option<T>>
типа. Я бы хотел, чтобы у Rust было сопоставление шаблонов на основе типов в общих файлах, но на самом деле это не так.
Я могу придумать два решения этой проблемы, но я не могу придумать, как их реализовать. Первым делом нужно было бы просмотреть все поля и найти Option
s, затем развернуть эти параметры и повторно обернуть их так, чтобы они имели только один Option
снаружи. Одна из потенциальных проблем с этим решением заключается в том, что мне, возможно, придется переписать их в другое Option
после выполнения вычислений. Вторым решением было бы найти Option
и соответствующим образом изменить сгенерированный код, чтобы, если внутренний параметр содержит None
, все это стало None
; в основном просто иметь вспомогательную функцию, которая выводит логическое значение, если поле является Option
. Есть идеи о том, как реализовать любой из них или лучшее решение?
Вот пример кода:
#[derive(Macro)]
struct X {
a: usize,
b: SomeType<String>,
c: Option<String>,
}
struct GeneratedX {
a: Option<usize>,
b: Option<SomeType<String>>,
c: Option<Option<String>>,
}
Используя подобную функцию, чтобы перенести все значения в параметры:
pub fn wrap_typ_in_options(amp;self) -> TokenStream {
// self is a struct with the type Type in it along with some other items.
let typ: syn::Type = self.typ();
// attribute to check if should ignore a field.
if self.should_ignore() {
quote! { Option<#typ> }
} else {
quote! { Option<<#typ as module::Trait>::Type> }
}
}
Комментарии:
1. Основная проблема в том, что вы можете просматривать только токены. Тип, подобный
MyFoo
, на самом деле может быть псевдонимом типа для необязательного. У вас не было бы способа обнаружить это.2. На самом деле немного неясно, о чем вы спрашиваете. Генерация полей структуры без вложенных параметров? Или иметь дело с преобразованиями значений?
3. Не беспокойтесь об псевдонимах, поскольку эта библиотека используется только для очень специфического проекта. И одно из моих решений использует первое, а другое — второе.
Ответ №1:
Я нашел решение этой проблемы, следуя второй идее, которая у меня была в исходном сообщении. Я использовал подобную функцию, чтобы определить, был ли токен Option
:
let idents_of_path = path
.segments
.iter()
.fold(String::new(), |mut acc, v| {
acc.push_str(amp;v.ident.to_string());
acc.push(':');
acc
});
vec!["Option:", "std:option:Option:", "core:option:Option:"]
.into_iter()
.find(|s| idents_of_path == *s)
.and_then(|_| path.segments.last())
Затем я добавил новый метод с именем, is_option
который возвращает логическое значение, если Type::Path
это был параметр.
pub fn is_option(amp;self) -> bool {
let typ = self.typ();
let opt = match typ {
Type::Path(typepath) if typepath.qself.is_none() => Some(typepath.path.clone()),
_ => None,
};
if let Some(o) = opt {
check_for_option(amp;o).is_some()
} else {
false
}
}
Я модифицировал сгенерированный код на основе результатов этого вызова способом, аналогичным тому, как я обрабатываю свои различные атрибуты. Все это должно работать нормально для моего конкретного варианта использования, поскольку никакие псевдонимы Option
никогда не будут введены в эту экосистему. Это немного запутанно, но на данный момент он выполняет свою работу.