Skip to content

Commit 42e3732

Browse files
committed
Auto merge of #41476 - abonander:book_proc_macro, r=nrc
Document the `proc_macro` feature in the Unstable Book Discusses the `proc_macro` feature flag and the features it enables: * Implicit enable of `extern_use_macros` feature and how to import proc macros * Error handling in proc macros (using panic messages) * Function-like proc macros using `#[proc_macro]` and a usage example for creating and invoking * Attribute-like proc macros using `#[proc_macro_attribute]` and a usage example for creating and invoking [Rendered](https://github.com/abonander/rust/blob/book_proc_macro/src/doc/unstable-book/src/language-features/proc-macro.md)
2 parents d8215fc + e616d12 commit 42e3732

File tree

1 file changed

+231
-0
lines changed

1 file changed

+231
-0
lines changed

src/doc/unstable-book/src/language-features/proc-macro.md

+231
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,236 @@ The tracking issue for this feature is: [#38356]
66

77
------------------------
88

9+
This feature flag guards the new procedural macro features as laid out by [RFC 1566], which alongside the now-stable
10+
[custom derives], provide stabilizable alternatives to the compiler plugin API (which requires the use of
11+
perma-unstable internal APIs) for programmatically modifying Rust code at compile-time.
912

13+
The two new procedural macro kinds are:
14+
15+
* Function-like procedural macros which are invoked like regular declarative macros, and:
1016

17+
* Attribute-like procedural macros which can be applied to any item which built-in attributes can
18+
be applied to, and which can take arguments in their invocation as well.
19+
20+
Additionally, this feature flag implicitly enables the [`use_extern_macros`](language-features/use-extern-macros.html) feature,
21+
which allows macros to be imported like any other item with `use` statements, as compared to
22+
applying `#[macro_use]` to an `extern crate` declaration. It is important to note that procedural macros may
23+
**only** be imported in this manner, and will throw an error otherwise.
24+
25+
You **must** declare the `proc_macro` feature in both the crate declaring these new procedural macro kinds as well as
26+
in any crates that use them.
27+
28+
### Common Concepts
29+
30+
As with custom derives, procedural macros may only be declared in crates of the `proc-macro` type, and must be public
31+
functions. No other public items may be declared in `proc-macro` crates, but private items are fine.
32+
33+
To declare your crate as a `proc-macro` crate, simply add:
34+
35+
```toml
36+
[lib]
37+
proc-macro = true
38+
```
39+
40+
to your `Cargo.toml`.
41+
42+
Unlike custom derives, however, the name of the function implementing the procedural macro is used directly as the
43+
procedural macro's name, so choose carefully.
44+
45+
Additionally, both new kinds of procedural macros return a `TokenStream` which *wholly* replaces the original
46+
invocation and its input.
47+
48+
#### Importing
49+
50+
As referenced above, the new procedural macros are not meant to be imported via `#[macro_use]` and will throw an
51+
error if they are. Instead, they are meant to be imported like any other item in Rust, with `use` statements:
52+
53+
```rust,ignore
54+
#![feature(proc_macro)]
55+
56+
// Where `my_proc_macros` is some crate of type `proc_macro`
57+
extern crate my_proc_macros;
58+
59+
// And declares a `#[proc_macro] pub fn my_bang_macro()` at its root.
60+
use my_proc_macros::my_bang_macro;
61+
62+
fn main() {
63+
println!("{}", my_bang_macro!());
64+
}
65+
```
66+
67+
#### Error Reporting
68+
69+
Any panics in a procedural macro implementation will be caught by the compiler and turned into an error message pointing
70+
to the problematic invocation. Thus, it is important to make your panic messages as informative as possible: use
71+
`Option::expect` instead of `Option::unwrap` and `Result::expect` instead of `Result::unwrap`, and inform the user of
72+
the error condition as unambiguously as you can.
73+
74+
#### `TokenStream`
75+
76+
The `proc_macro::TokenStream` type is hardcoded into the signatures of procedural macro functions for both input and
77+
output. It is a wrapper around the compiler's internal representation for a given chunk of Rust code.
78+
79+
### Function-like Procedural Macros
80+
81+
These are procedural macros that are invoked like regular declarative macros. They are declared as public functions in
82+
crates of the `proc_macro` type and using the `#[proc_macro]` attribute. The name of the declared function becomes the
83+
name of the macro as it is to be imported and used. The function must be of the kind `fn(TokenStream) -> TokenStream`
84+
where the sole argument is the input to the macro and the return type is the macro's output.
85+
86+
This kind of macro can expand to anything that is valid for the context it is invoked in, including expressions and
87+
statements, as well as items.
88+
89+
**Note**: invocations of this kind of macro require a wrapping `[]`, `{}` or `()` like regular macros, but these do not
90+
appear in the input, only the tokens between them. The tokens between the braces do not need to be valid Rust syntax.
91+
92+
<span class="filename">my_macro_crate/src/lib.rs</span>
93+
94+
```rust,ignore
95+
#![feature(proc_macro)]
96+
97+
// This is always necessary to get the `TokenStream` typedef.
98+
extern crate proc_macro;
99+
100+
use proc_macro::TokenStream;
101+
102+
#[proc_macro]
103+
pub fn say_hello(_input: TokenStream) -> TokenStream {
104+
// This macro will accept any input because it ignores it.
105+
// To enforce correctness in macros which don't take input,
106+
// you may want to add `assert!(_input.to_string().is_empty());`.
107+
"println!(\"Hello, world!\")".parse().unwrap()
108+
}
109+
```
110+
111+
<span class="filename">my_macro_user/Cargo.toml</span>
112+
113+
```toml
114+
[dependencies]
115+
my_macro_crate = { path = "<relative path to my_macro_crate>" }
116+
```
117+
118+
<span class="filename">my_macro_user/src/lib.rs</span>
119+
120+
```rust,ignore
121+
#![feature(proc_macro)]
122+
123+
extern crate my_macro_crate;
124+
125+
use my_macro_crate::say_hello;
126+
127+
fn main() {
128+
say_hello!();
129+
}
130+
```
131+
132+
As expected, this prints `Hello, world!`.
133+
134+
### Attribute-like Procedural Macros
135+
136+
These are arguably the most powerful flavor of procedural macro as they can be applied anywhere attributes are allowed.
137+
138+
They are declared as public functions in crates of the `proc-macro` type, using the `#[proc_macro_attribute]` attribute.
139+
The name of the function becomes the name of the attribute as it is to be imported and used. The function must be of the
140+
kind `fn(TokenStream, TokenStream) -> TokenStream` where:
141+
142+
The first argument represents any metadata for the attribute (see [the reference chapter on attributes][refr-attr]).
143+
Only the metadata itself will appear in this argument, for example:
144+
145+
* `#[my_macro]` will get an empty string.
146+
* `#[my_macro = "string"]` will get `= "string"`.
147+
* `#[my_macro(ident)]` will get `(ident)`.
148+
* etc.
149+
150+
The second argument is the item that the attribute is applied to. It can be a function, a type definition,
151+
an impl block, an `extern` block, or a module—attribute invocations can take the inner form (`#![my_attr]`)
152+
or outer form (`#[my_attr]`).
153+
154+
The return type is the output of the macro which *wholly* replaces the item it was applied to. Thus, if your intention
155+
is to merely modify an item, it *must* be copied to the output. The output must be an item; expressions, statements
156+
and bare blocks are not allowed.
157+
158+
There is no restriction on how many items an attribute-like procedural macro can emit as long as they are valid in
159+
the given context.
160+
161+
<span class="filename">my_macro_crate/src/lib.rs</span>
162+
163+
```rust,ignore
164+
#![feature(proc_macro)]
165+
166+
extern crate proc_macro;
167+
168+
use proc_macro::TokenStream;
169+
170+
/// Adds a `/// ### Panics` docstring to the end of the input's documentation
171+
///
172+
/// Does not assert that its receiver is a function or method.
173+
#[proc_macro_attribute]
174+
pub fn panics_note(args: TokenStream, input: TokenStream) -> TokenStream {
175+
let args = args.to_string();
176+
let mut input = input.to_string();
177+
178+
assert!(args.starts_with("= \""), "`#[panics_note]` requires an argument of the form \
179+
`#[panics_note = \"panic note here\"]`");
180+
181+
// Get just the bare note string
182+
let panics_note = args.trim_matches(&['=', ' ', '"'][..]);
183+
184+
// The input will include all docstrings regardless of where the attribute is placed,
185+
// so we need to find the last index before the start of the item
186+
let insert_idx = idx_after_last_docstring(&input);
187+
188+
// And insert our `### Panics` note there so it always appears at the end of an item's docs
189+
input.insert_str(insert_idx, &format!("/// # Panics \n/// {}\n", panics_note));
190+
191+
input.parse().unwrap()
192+
}
193+
194+
// `proc-macro` crates can contain any kind of private item still
195+
fn idx_after_last_docstring(input: &str) -> usize {
196+
// Skip docstring lines to find the start of the item proper
197+
input.lines().skip_while(|line| line.trim_left().starts_with("///")).next()
198+
// Find the index of the first non-docstring line in the input
199+
// Note: assumes this exact line is unique in the input
200+
.and_then(|line_after| input.find(line_after))
201+
// No docstrings in the input
202+
.unwrap_or(0)
203+
}
204+
```
205+
206+
<span class="filename">my_macro_user/Cargo.toml</span>
207+
208+
```toml
209+
[dependencies]
210+
my_macro_crate = { path = "<relative path to my_macro_crate>" }
211+
```
212+
213+
<span class="filename">my_macro_user/src/lib.rs</span>
214+
215+
```rust,ignore
216+
#![feature(proc_macro)]
217+
218+
extern crate my_macro_crate;
219+
220+
use my_macro_crate::panics_note;
221+
222+
/// Do the `foo` thing.
223+
#[panics_note = "Always."]
224+
pub fn foo() {
225+
panic!()
226+
}
227+
```
228+
229+
Then the rendered documentation for `pub fn foo` will look like this:
230+
231+
> `pub fn foo()`
232+
>
233+
> ----
234+
> Do the `foo` thing.
235+
> # Panics
236+
> Always.
237+
238+
[RFC 1566]: https://github.com/rust-lang/rfcs/blob/master/text/1566-proc-macros.md
239+
[custom derives]: https://doc.rust-lang.org/book/procedural-macros.html
240+
[rust-lang/rust#41430]: https://github.com/rust-lang/rust/issues/41430
241+
[refr-attr]: https://doc.rust-lang.org/reference/attributes.html

0 commit comments

Comments
 (0)