Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to embed a Rust macro variable into documentation?

I'd like to use a macro variable in the macro-generated documentation:

macro_rules! impl_foo {
    ($name:ident) => {
        /// Returns a new `$name`.
        fn myfoo() -> $name {

        }
    };
}

However, the variable won't be substituted. I also tried using the #[doc] attribute:

macro_rules! impl_foo {
    ($name:ident) => {
        #[doc = concat!("Returns a new `", $name, "`.")]
        fn myfoo() -> $name {

        }
    };
}

This one even fails to parse: unexpected token: 'concat'

like image 970
torkleyy Avatar asked Apr 11 '17 17:04

torkleyy


People also ask

Can you use macros on Rust?

The most widely used form of macros in Rust is the declarative macro. These are also sometimes referred to as “macros by example,” “ macro_rules! macros,” or just plain “macros.” At their core, declarative macros allow you to write something similar to a Rust match expression.

How do I make a macro in Rust?

To write a custom derive macro in Rust, we can use DeriveInput for parsing input to derive macro. We'll also use the proc_macro_derive macro to define a custom derive macro. More advanced procedural macros can be written using syn . Check out this example from syn 's repo.

What does tt mean in Rust?

I'm reading a book about Rust, and start playing with Rust macros. All metavariable types are explained there and have examples, except the last one – tt . According to the book, it is a “a single token tree”.

What is Proc macro in Rust?

Procedural macros allow you to run code at compile time that operates over Rust syntax, both consuming and producing Rust syntax. You can sort of think of procedural macros as functions from an AST to another AST. Procedural macros must be defined in a crate with the crate type of proc-macro .


2 Answers

While the answer @mcarton gave does work perfectly fine for simple examples, it breaks a bit for more complicated ones. Rustdoc seems to insert spaces between the different doc attributes. The markdown processor strips them out most of the time, but sometimes, it transform them to spaces instead. Consider this example:

macro_rules! impl_foo {
    ($name:ident, $sname:expr) => {
        #[doc = "You can call this as `myfoo("]
        #[doc = $sname]
        #[doc = ")`."]
        pub fn myfoo(_: $name) -> $name {
            42
        }
    };

    ($name:tt) => {
        impl_foo!($name, stringify!($name));
    };
}

impl_foo!(i32);


fn main() {
    println!("Hello, world!");
}

This should generate the documentation "You can call this as myfoo(i32).", but in reality, it results in "You can call this as myfoo( i32 )." (note the additional spaces):Screenshot from the rustdoc page showing the additional spaces

I'm not too sure whether my solution would have worked with the 2017 rustc back when the question was asked, but in modern Rust, this can be done by combining stringify! with concat!:

macro_rules! impl_foo {
    ($name:tt) => {
        #[doc = concat!("You can call this as `myfoo(", stringify!($name), ")`.")]
        pub fn myfoo(_: $name) -> $name {
            42
        }
    };
}

impl_foo!(i32);


fn main() {
    println!("Hello, world!");
}

This results in the documentation you want (so, without superfluous spaces):

Screenshot from the fixed rustdoc page

like image 172
Elias Holzmann Avatar answered Oct 02 '22 20:10

Elias Holzmann


This can be done using a recursive macro:

macro_rules! impl_foo {
    ($name:ident, $sname:expr) => {
        #[doc = "Returns a new `"]
        #[doc = $sname]
        #[doc = "`."]
        pub fn myfoo() -> $name {
            42
        }
    };

    ($name:tt) => {
        impl_foo!($name, stringify!($name));
    };
}

impl_foo!(u32);


fn main() {
    println!("Hello, world!");
}

Which renders as:

Example from rustdoc

like image 36
mcarton Avatar answered Oct 02 '22 18:10

mcarton