Как я могу сохранить идентификатор (`proc_macro :: Ident`) как константу, чтобы избежать его повторения? - PullRequest
4 голосов
/ 07 января 2020

Я пишу процедурный макрос, и мне нужно многократно выдавать очень длинный идентификатор (например, из-за гигиены ). Я использую quote! для создания TokenStream с, но я не хочу повторять длинный идентификатор снова и снова!

Например, я хочу сгенерировать этот код:

let very_long_ident_is_very_long_indeed = 3;
println!("{}", very_long_ident_is_very_long_indeed);
println!("twice: {}", very_long_ident_is_very_long_indeed + very_long_ident_is_very_long_indeed);

Я знаю, что могу создать Ident и интерполировать его в quote!:

let my_ident = Ident::new("very_long_ident_is_very_long_indeed", Span::call_site());
quote! {
    let #my_ident = 3;
    println!("{}", #my_ident);
    println!("twice: {}", #my_ident + #my_ident);
}

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

const FOO: Ident = Ident::new("very_long_ident_is_very_long_indeed", Span::call_site());

С этой ошибкой:

error[E0015]: calls in constants are limited to constant functions, tuple structs and tuple variants
 --> src/lib.rs:5:70
  |
5 | const FOO: Ident = Ident::new("very_long_ident_is_very_long_indeed", Span::call_site());
  |                                                                      ^^^^^^^^^^^^^^^^^

error[E0015]: calls in constants are limited to constant functions, tuple structs and tuple variants
 --> src/lib.rs:5:20
  |
5 | const FOO: Ident = Ident::new("very_long_ident_is_very_long_indeed", Span::call_site());
  |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Я сомневаюсь, что эти функции будут отмечены const в ближайшее время.

Я мог бы сделать саму строку константой:

const IDENT: &str = "very_long_ident_is_very_long_indeed";

Но тогда, где бы я ни хотел использовать идентификатор, мне нужно вызывать Ident::new(IDENT, Span::call_site()), что было бы довольно раздражающе. Я хочу просто написать #IDENT в моем quote! вызове. Можно ли как-нибудь заставить это работать?

1 Ответ

7 голосов
/ 07 января 2020

К счастью, есть способ!

Интерполяция через # в quote! работает через черту ToTokens . Все, что реализует эту черту, можно интерполировать. Поэтому нам просто нужно создать тип, который может быть сконструирован в константу и который реализует ToTokens. Черта использует типы от proc-macro2 вместо стандартного proc-macro.

use proc_macro2::{Ident, Span, TokenStream};


struct IdentHelper(&'static str);

impl quote::ToTokens for IdentHelper {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        Ident::new(self.0, Span::call_site()).to_tokens(tokens)
    }
}

Теперь вы можете определить свой идентификатор:

const IDENT: IdentHelper = IdentHelper("very_long_ident_is_very_long_indeed");

И напрямую использовать его в quote!:

quote! {
    let #IDENT = 3;
}

( Полный пример )

...