Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand documentation on procedural macros #405

Closed
wants to merge 1 commit into from

Conversation

alexcrichton
Copy link
Member

In preparation for the 1.30 stabilization I figured I'd get started and help
write some documentation!

@alexcrichton
Copy link
Member Author

cc @dtolnay

which is a far more stable interface over time for both the compiler and for
procedural macros to target.

A *token stream* is roughly equivalent to `Vec<TokenTree>` where a `TokenTree`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhere it would be good to mention the key difference between TokenStream and Vec<TokenTree> -- that TokenStream is cheap to clone. Though maybe this belongs in rustdoc of the TokenStream type.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This definitely belongs in the library documentation and not the reference. We mostly just care that the type exists.


```rust,ignore
#[proc_macro]
pub fn foo(x: TokenStream) -> TokenStream {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of x I would call it input.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I write the signature for procedural attributes in my branch like so:

Attribute macros are defined by a [public] function with
the `proc_macro_attribute` attribute that a signature of `(TokenStream,
TokenStream) -> TokenStream`.

#### Custom attributes on Custom derive

An additional feature of custom derive macros is that they can whitelist names
of attributes which are considered not part of normal attribute macro expansion.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should pick a clear name for such attributes to distinguish them from attributes that are procedural macros. This is one of the most confusing aspects of the procedural macro API (practically everybody believes #[serde(...)] is itself a macro).

I have been using the terms "attribute macro" and "inert attribute" to distinguish the two.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recently had to come up with a name for this in the compiler and used "derive helper attribute".

"Inert attribute", if it means non-macro attribute, an attribute that doesn't expand the item it's applied to, can currently refer to a built-in attribute (#[inline]), a tool attribute (#[rustfmt::skip]), a "derive helper" (#[serde(...)]), or a "custom" attribute on nightly (feature(custom_attribute) + #[my_attr]).

function. This function must have the type signature:
* Custom macros - `my_macro!(...)`
* Custom derive - `#[derive(MyTrait)]`
* Custom attributes - `#[my_attribute]`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's kill the word "custom". It means nothing. The word should not appear on this page. We don't call things "custom crates" and "custom functions" and "custom structs".

I would write:

  • Function-like macros / bang macros
  • Derive macros
  • Attribute macros

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM.


```rust,ignore
#[proc_macro_attribute]
pub fn foo(attr: TokenStream, item: TokenStream) -> TokenStream {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second argument is not necessarily an item if this attribute is on a statement or expression. I would name the two args and input. Their role is fully analogous to command-line arguments and command-line input such as in:

$ input | ./foo a b c
#[foo(a, b, c)]
struct Input;

where a b c are arguments and separately there is input (the "in" in "stdin").

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Custom attributes cannot be applied to non-items right now, so this is moot. If that changes before the final release, we can change it. I think args is a terrible name, even with the analogy. Though could be a good name for teaching. I opt for just showing the signature without naming them.

This is primarily due to the lack of hygiene with procedural macros. Once a
better hygiene story is stabilized this restriction will likely be lifted.

* Procedural macros cannot expand to definitions of `macro_rules!` macros (none
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, serde_derive definitely does this now and it's stable. Maybe I misunderstand what this bullet means. Our two derives each include a macro_rules helper macro in the generated code that is called from elsewhere in the generated code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This restriction is false for derive macros. Also missing that cannot expand to module items.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The restriction was implemented much later than derives were stabilized (rust-lang/rust#50820), so it was applied to fn-like macros and attribute macros to avoid breakage.

So macro_rules generated from derives do not produce immediate errors, but they can still be buggy and perhaps go through some breakage later with hygiene changes.

@Havvy
Copy link
Contributor

Havvy commented Aug 23, 2018

I wish I knew you were working on this. I too have been working on it since Rustconf ended.

that defines an interface for building a procedural macro. The
`#[proc_macro_derive(Foo)]` attribute is used to mark the deriving
function. This function must have the type signature:
* Custom macros - `my_macro!(...)`
Copy link
Contributor

@Havvy Havvy Aug 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought function-like macros weren't being stabilized. Edit: I just tried it, and it works.

In preparation for the 1.30 stabilization I figured I'd get started and help
write some documentation!
@alexcrichton
Copy link
Member Author

Updated!

Copy link
Contributor

@Havvy Havvy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh. I thought I submitted this two days ago...

This review is sort of weird because I was working on the same exact thing, and I want to merge them.

The main thing to note is that this page currently makes a great guide. But the reference isn't a guide, so quite a lot of this is useful w.r.t. the reference. So go publish this as is (or listen to @dtolnay first). Perhaps it can be modified into being the chapter on procedural macros in TRPL.

Overall, there's a lot of great information here that I would have had to spend a lot of time finding and figuring out how to actually write it. I marked the paragraphs that were especially helpful as "great" or "excellent". The general structure of sections is also pretty good. A little rearrangement to be referencey (e.g. cross-cutting things first instead of mostly at the end) is needed, but it's better than what I would have ultimately came up with.

A lot of comments were also to myself, since my plan of action with this is to remove guide and future speaking terms and then fit in the information from my own branch.


### Crates and procedural macros

All procedural macros in Rust are compiled as a crate. Procedural macro crates
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in Rust is never necessary. Everything about the reference is about Rust unless specified otherwise.


### Crates and procedural macros

All procedural macros in Rust are compiled as a crate. Procedural macro crates
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first sentence doesn't really make sense. It's asserting that a procedural macro is a crate. Here's what I wrote in my branch: Procedural macros must be defined in a crate with the [crate type] of `proc_macro`​ with "crate type" linking to linkage.html.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second sentence is Cargo-specific. And currently we have a policy about being compiler agnostic. That said, I did want to bend this rule and put this in a note. That can be done by starting the first line with > Note: and every other line with >.

Procedural macros are always compiled with the same target as the compiler
itself. For example if you execute `cargo build --target
arm-unknown-linux-gnueabi` then procedural macros will not be compiled for ARM,
but rather your build computer (for example `x86_64-unknown-linux-gnu`).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This paragraph is also less about procedural macros themselves and more about the proc_macro crate type. That said, this isn't currently documented about that crate type and it's not something I would have thought about to add either. So it's definitely good to have it documented somewhere. So want to move this to linkage.html?

We might want to move over the proc_macro crate type information to this page at some point, but for now, I'd like to keep all crate type information in linkage.html for now.

facilities to working with the types of each procedural macro function. You can
learn more about this crate by exploring [the documentation][pm-dox].

[pm-dox]: https://doc.rust-lang.org/stable/proc_macro/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per #391 we're moving to a consistent style of links all at the bottom of the page.


### The `proc_macro` crate

Procedural macro crates almost always will link to the in-tree `proc_macro`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was struggling so hard with expressing this information. This entire paragraph is perfect! 😍 Well, almost. I'd replace "in-tree" with "compiler-provided" and remove "compiler-provided" from the second sentence.

Everything after this paragraph in this subsection is guide level and can be removed. The minimal examples for attribute macros and deriver macros should show that.

Controlling spans is quite a powerful feature and needs to be used with care,
misuse can lead to some excessively confusing error messages!

### Procedural macros and hygiene
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good section. Think we can just call it "Hygiene" though. Should also move to before discussing the specific kinds of procedural macros.


### Procedural macros and hygiene

Currently all procedural macros are "unhygienic". This means that all procedural
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is close enough to a definition that we should put it in italics instead of quotes.

conservative limitation while compiler bugs are worked through and a design is
agreed upon.

* Procedural attributes can only be attached to items, not expressions. For
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really part of attributes.html. Might be useful to repeat in proc-macros.html anyways.

This is primarily due to the lack of hygiene with procedural macros. Once a
better hygiene story is stabilized this restriction will likely be lifted.

* Procedural macros cannot expand to definitions of `macro_rules!` macros (none
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This restriction is false for derive macros. Also missing that cannot expand to module items.

better hygiene story is stabilized this restriction will likely be lifted.

* Procedural macros cannot expand to definitions of `macro_rules!` macros (none
of them). This restriction may be lifted over time but for now this is a
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talking about the future.

@alexcrichton
Copy link
Member Author

Sorry @Havvy I've been really busy recently and haven't had a chance to come back to your comments here, but it looks like you've definitely got things in hand with #412, thanks for picking this up!

@alexcrichton alexcrichton deleted the macros branch September 3, 2018 17:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants