Skip to content

Commit d153e13

Browse files
committed
Add notes on migrating crates to work with both editions
1 parent 21f73fd commit d153e13

File tree

1 file changed

+157
-0
lines changed

1 file changed

+157
-0
lines changed

src/rust-2018/macros/macro-changes.md

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
![Minimum Rust version: beta](https://img.shields.io/badge/Minimum%20Rust%20Version-beta-orange.svg)
44

5+
## `macro_rules!` style macros
6+
57
In Rust 2018, you can import specific macros from external crates via `use`
68
statements, rather than the old `#[macro_use]` attribute.
79

@@ -78,3 +80,158 @@ struct Bar;
7880

7981
This only works for macros defined in external crates.
8082
For macros defined locally, `#[macro_use] mod foo;` is still required, as it was in Rust 2015.
83+
84+
### Local helper macros
85+
86+
Sometimes it is helpful or necessary to have helper macros inside your module. This can make
87+
supporting both versions of rust more complicated.
88+
89+
For example, let's make a simplified (and slightly contrived) version of the `log` crate in 2015
90+
edition style:
91+
92+
```rust,ignore
93+
pub struct LogLevel {
94+
Warn,
95+
Error
96+
}
97+
98+
#[doc(hidden)]
99+
#[macro_export]
100+
macro_rules! log {
101+
($level:expr, $msg:expr) => {
102+
println!("{}: {}", $level, $msg)
103+
}
104+
}
105+
106+
#[macro_export]
107+
macro_rules! warn {
108+
($msg:expr) => {
109+
log!(stringify!($crate::LogLevel::Warn), $msg)
110+
}
111+
}
112+
113+
#[macro_export]
114+
macro_rules! error {
115+
($msg:expr) => {
116+
log!(stringify!($crate::LogLevel::Error), $msg)
117+
}
118+
}
119+
```
120+
121+
Our `log!` macro is private to our module, but needs to be exported as it is called by other
122+
macros, and in 2015 edition all used macros must be exported.
123+
124+
Now, in 2018 this example will not compile:
125+
126+
```rust,ignore
127+
use log::error;
128+
129+
fn main() {
130+
error!("error message");
131+
}
132+
```
133+
134+
will give an error message about not finding the `log!` macro. This is because unlike in the 2015
135+
edition, macros are namespaced and we must import them. We could do
136+
137+
```rust,ignore
138+
use log::{log, error};
139+
```
140+
141+
which would make our code compile, but `log` is meant to be an implementation detail!
142+
143+
#### Macros with `$crate::` prefix.
144+
145+
The cleanest way to handle this situation is to use the `$crate::` prefix for macros, the same as
146+
you would for any other path. Versions of the compiler >= 1.30 will handle this in both editions:
147+
148+
```rust,ignore
149+
macro_rules! warn {
150+
($msg:expr) => {
151+
$crate::log!(stringify!($crate::LogLevel::Warn), $msg)
152+
}
153+
}
154+
155+
// ...
156+
```
157+
158+
However, this will not work for older versions of the compiler, that don't understand the
159+
`$crate::` prefix for macros.
160+
161+
#### Macros using `local_inner_macros`
162+
163+
We also have the `local_inner_macros` modifier that we can add to our `#[macro_export]` attribute.
164+
This has the advantage of working with older rustc versions (older versions just ignore the extra
165+
modifier). The downside is that it's a bit messier:
166+
167+
```rust,ignore
168+
#[macro_export(local_inner_macros)]
169+
macro_rules! warn {
170+
($msg:expr) => {
171+
log!(stringify!($crate::LogLevel::Warn), $msg)
172+
}
173+
}
174+
```
175+
176+
So the code knows to look for any macros used locally. But wait - this won't compile, because we
177+
use the `stringify!` macro that isn't in our local crate (hence the convoluted example). The
178+
solution is to add a level of indirection: we crate a macro that wraps stringify, but is local to
179+
our crate. That way everything works in both editions (sadly we have to pollute the global
180+
namespace a bit, but that's ok).
181+
182+
```rust,ignore
183+
#[doc(hidden)]
184+
#[macro_export]
185+
macro_rules! my_special_stringify {
186+
($($inner:tt)*) => {
187+
stringify!($($inner)*)
188+
}
189+
}
190+
```
191+
192+
Here we're using the most general macro pattern possible, a list of token trees. We just pass
193+
whatever tokens we get to the inner macro, and rely on it to report errors.
194+
195+
So the full 2015/2018 working example would be:
196+
197+
```rust,ignore
198+
pub struct LogLevel {
199+
Warn,
200+
Error
201+
}
202+
203+
#[doc(hidden)]
204+
#[macro_export]
205+
macro_rules! log {
206+
($level:expr, $msg:expr) => {
207+
println!("{}: {}", $level, $msg)
208+
}
209+
}
210+
211+
#[macro_export(local_inner_macros)]
212+
macro_rules! warn {
213+
($msg:expr) => {
214+
log!(my_special_stringify!($crate::LogLevel::Warn), $msg)
215+
}
216+
}
217+
218+
#[macro_export(local_inner_macros)]
219+
macro_rules! error {
220+
($msg:expr) => {
221+
log!(my_special_stringify!($crate::LogLevel::Error), $msg)
222+
}
223+
}
224+
225+
#[doc(hidden)]
226+
#[macro_export]
227+
macro_rules! my_special_stringify {
228+
($($args:tt)*) => {
229+
stringify!($($args)*)
230+
}
231+
}
232+
```
233+
234+
Once everyone is using a rustc version >= 1.30, we can all just use the `$crate::` method (2015
235+
crates are guaranteed to carry on compiling fine with later versions of the compiler). We need to
236+
wait for package managers and larger organisations to update their compilers before this happens,
237+
so in the mean time we can use the `local_inner_macros` method to support everybody. :)

0 commit comments

Comments
 (0)