From d153e13cee3be02244da148d713a083507ea8a81 Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Tue, 23 Oct 2018 10:36:52 +0100 Subject: [PATCH 1/7] Add notes on migrating crates to work with both editions --- src/rust-2018/macros/macro-changes.md | 157 ++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/src/rust-2018/macros/macro-changes.md b/src/rust-2018/macros/macro-changes.md index 1d43db67..93bdd238 100644 --- a/src/rust-2018/macros/macro-changes.md +++ b/src/rust-2018/macros/macro-changes.md @@ -2,6 +2,8 @@ ![Minimum Rust version: beta](https://img.shields.io/badge/Minimum%20Rust%20Version-beta-orange.svg) +## `macro_rules!` style macros + In Rust 2018, you can import specific macros from external crates via `use` statements, rather than the old `#[macro_use]` attribute. @@ -78,3 +80,158 @@ struct Bar; This only works for macros defined in external crates. For macros defined locally, `#[macro_use] mod foo;` is still required, as it was in Rust 2015. + +### Local helper macros + +Sometimes it is helpful or necessary to have helper macros inside your module. This can make +supporting both versions of rust more complicated. + +For example, let's make a simplified (and slightly contrived) version of the `log` crate in 2015 +edition style: + +```rust,ignore +pub struct LogLevel { + Warn, + Error +} + +#[doc(hidden)] +#[macro_export] +macro_rules! log { + ($level:expr, $msg:expr) => { + println!("{}: {}", $level, $msg) + } +} + +#[macro_export] +macro_rules! warn { + ($msg:expr) => { + log!(stringify!($crate::LogLevel::Warn), $msg) + } +} + +#[macro_export] +macro_rules! error { + ($msg:expr) => { + log!(stringify!($crate::LogLevel::Error), $msg) + } +} +``` + +Our `log!` macro is private to our module, but needs to be exported as it is called by other +macros, and in 2015 edition all used macros must be exported. + +Now, in 2018 this example will not compile: + +```rust,ignore +use log::error; + +fn main() { + error!("error message"); +} +``` + +will give an error message about not finding the `log!` macro. This is because unlike in the 2015 +edition, macros are namespaced and we must import them. We could do + +```rust,ignore +use log::{log, error}; +``` + +which would make our code compile, but `log` is meant to be an implementation detail! + +#### Macros with `$crate::` prefix. + +The cleanest way to handle this situation is to use the `$crate::` prefix for macros, the same as +you would for any other path. Versions of the compiler >= 1.30 will handle this in both editions: + +```rust,ignore +macro_rules! warn { + ($msg:expr) => { + $crate::log!(stringify!($crate::LogLevel::Warn), $msg) + } +} + +// ... +``` + +However, this will not work for older versions of the compiler, that don't understand the +`$crate::` prefix for macros. + +#### Macros using `local_inner_macros` + +We also have the `local_inner_macros` modifier that we can add to our `#[macro_export]` attribute. +This has the advantage of working with older rustc versions (older versions just ignore the extra +modifier). The downside is that it's a bit messier: + +```rust,ignore +#[macro_export(local_inner_macros)] +macro_rules! warn { + ($msg:expr) => { + log!(stringify!($crate::LogLevel::Warn), $msg) + } +} +``` + +So the code knows to look for any macros used locally. But wait - this won't compile, because we +use the `stringify!` macro that isn't in our local crate (hence the convoluted example). The +solution is to add a level of indirection: we crate a macro that wraps stringify, but is local to +our crate. That way everything works in both editions (sadly we have to pollute the global +namespace a bit, but that's ok). + +```rust,ignore +#[doc(hidden)] +#[macro_export] +macro_rules! my_special_stringify { + ($($inner:tt)*) => { + stringify!($($inner)*) + } +} +``` + +Here we're using the most general macro pattern possible, a list of token trees. We just pass +whatever tokens we get to the inner macro, and rely on it to report errors. + +So the full 2015/2018 working example would be: + +```rust,ignore +pub struct LogLevel { + Warn, + Error +} + +#[doc(hidden)] +#[macro_export] +macro_rules! log { + ($level:expr, $msg:expr) => { + println!("{}: {}", $level, $msg) + } +} + +#[macro_export(local_inner_macros)] +macro_rules! warn { + ($msg:expr) => { + log!(my_special_stringify!($crate::LogLevel::Warn), $msg) + } +} + +#[macro_export(local_inner_macros)] +macro_rules! error { + ($msg:expr) => { + log!(my_special_stringify!($crate::LogLevel::Error), $msg) + } +} + +#[doc(hidden)] +#[macro_export] +macro_rules! my_special_stringify { + ($($args:tt)*) => { + stringify!($($args)*) + } +} +``` + +Once everyone is using a rustc version >= 1.30, we can all just use the `$crate::` method (2015 +crates are guaranteed to carry on compiling fine with later versions of the compiler). We need to +wait for package managers and larger organisations to update their compilers before this happens, +so in the mean time we can use the `local_inner_macros` method to support everybody. :) From 19dc8a8082e3f15a2328a8cbd10fe6943f8f363d Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Tue, 23 Oct 2018 10:43:15 +0100 Subject: [PATCH 2/7] Style improvement --- src/rust-2018/macros/macro-changes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rust-2018/macros/macro-changes.md b/src/rust-2018/macros/macro-changes.md index 93bdd238..9d12930c 100644 --- a/src/rust-2018/macros/macro-changes.md +++ b/src/rust-2018/macros/macro-changes.md @@ -155,7 +155,7 @@ macro_rules! warn { // ... ``` -However, this will not work for older versions of the compiler, that don't understand the +However, this will not work for older versions of the compiler that don't understand the `$crate::` prefix for macros. #### Macros using `local_inner_macros` From 5be4ab32ba65362b9922e39f0a1d9459784cbcb3 Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Thu, 25 Oct 2018 13:35:49 +0100 Subject: [PATCH 3/7] Make recommended changes --- src/rust-2018/macros/macro-changes.md | 94 ++++++++++++++++++--------- 1 file changed, 62 insertions(+), 32 deletions(-) diff --git a/src/rust-2018/macros/macro-changes.md b/src/rust-2018/macros/macro-changes.md index 9d12930c..0d755672 100644 --- a/src/rust-2018/macros/macro-changes.md +++ b/src/rust-2018/macros/macro-changes.md @@ -90,35 +90,49 @@ For example, let's make a simplified (and slightly contrived) version of the `lo edition style: ```rust,ignore +/// How important/severe the log message is. +#[derive(Copy, Clone)] pub struct LogLevel { Warn, Error } +impl fmt::Display for LogLevel { + pub fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + LogLevel::Warn => write!(f, "warning"), + LogLevel::Error => write!(f, "error"), + } + } +} + +// A helper macro to log the message. #[doc(hidden)] #[macro_export] -macro_rules! log { - ($level:expr, $msg:expr) => { +macro_rules! __impl_log { + ($level:expr, $msg:expr) => {{ println!("{}: {}", $level, $msg) - } + }} } +/// Warn level log message #[macro_export] macro_rules! warn { - ($msg:expr) => { - log!(stringify!($crate::LogLevel::Warn), $msg) + ($($args:tt)*) => { + __impl_log!($crate::LogLevel::Warn, format_args!($($args)*)) } } +/// Error level log message #[macro_export] macro_rules! error { - ($msg:expr) => { - log!(stringify!($crate::LogLevel::Error), $msg) + ($($args:tt)*) => { + __impl_log!($crate::LogLevel::Error, format_args!($($args)*)) } } ``` -Our `log!` macro is private to our module, but needs to be exported as it is called by other +Our `__impl_log!` macro is private to our module, but needs to be exported as it is called by other macros, and in 2015 edition all used macros must be exported. Now, in 2018 this example will not compile: @@ -131,14 +145,14 @@ fn main() { } ``` -will give an error message about not finding the `log!` macro. This is because unlike in the 2015 -edition, macros are namespaced and we must import them. We could do +will give an error message about not finding the `__impl_log!` macro. This is because unlike in +the 2015 edition, macros are namespaced and we must import them. We could do ```rust,ignore -use log::{log, error}; +use log::{__impl_log, error}; ``` -which would make our code compile, but `log` is meant to be an implementation detail! +which would make our code compile, but `__impl_log` is meant to be an implementation detail! #### Macros with `$crate::` prefix. @@ -147,8 +161,8 @@ you would for any other path. Versions of the compiler >= 1.30 will handle this ```rust,ignore macro_rules! warn { - ($msg:expr) => { - $crate::log!(stringify!($crate::LogLevel::Warn), $msg) + ($($args:tt)*) => { + $crate::__impl_log!($crate::LogLevel::Warn, format_args!($($args)*)) } } @@ -167,24 +181,26 @@ modifier). The downside is that it's a bit messier: ```rust,ignore #[macro_export(local_inner_macros)] macro_rules! warn { - ($msg:expr) => { - log!(stringify!($crate::LogLevel::Warn), $msg) + ($($args:tt)*) => { + __impl_log!($crate::LogLevel::Warn, format_args!($($args)*)) } } ``` So the code knows to look for any macros used locally. But wait - this won't compile, because we -use the `stringify!` macro that isn't in our local crate (hence the convoluted example). The -solution is to add a level of indirection: we crate a macro that wraps stringify, but is local to -our crate. That way everything works in both editions (sadly we have to pollute the global +use the `format_args!` macro that isn't in our local crate (hence the convoluted example). The +solution is to add a level of indirection: we crate a macro that wraps `format_args`, but is local +to our crate. That way everything works in both editions (sadly we have to pollute the global namespace a bit, but that's ok). ```rust,ignore +// I've used the pattern `___` to name this macro, hopefully avoiding +// name clashes. #[doc(hidden)] #[macro_export] -macro_rules! my_special_stringify { +macro_rules! _log__format_args { ($($inner:tt)*) => { - stringify!($($inner)*) + format_args! { $($inner)* } } } ``` @@ -194,39 +210,53 @@ whatever tokens we get to the inner macro, and rely on it to report errors. So the full 2015/2018 working example would be: -```rust,ignore +```rust +/// How important/severe the log message is. +#[derive(Copy, Clone)] pub struct LogLevel { Warn, Error } +impl fmt::Display for LogLevel { + pub fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + LogLevel::Warn => write!(f, "warning"), + LogLevel::Error => write!(f, "error"), + } + } +} + +// A helper macro to log the message. #[doc(hidden)] #[macro_export] -macro_rules! log { - ($level:expr, $msg:expr) => { +macro_rules! __impl_log { + ($level:expr, $msg:expr) => {{ println!("{}: {}", $level, $msg) - } + }} } +/// Warn level log message #[macro_export(local_inner_macros)] macro_rules! warn { - ($msg:expr) => { - log!(my_special_stringify!($crate::LogLevel::Warn), $msg) + ($($args:tt)*) => { + __impl_log!($crate::LogLevel::Warn, format_args!($($args)*)) } } +/// Error level log message #[macro_export(local_inner_macros)] macro_rules! error { - ($msg:expr) => { - log!(my_special_stringify!($crate::LogLevel::Error), $msg) + ($($args:tt)*) => { + __impl_log!($crate::LogLevel::Error, format_args!($($args)*)) } } #[doc(hidden)] #[macro_export] -macro_rules! my_special_stringify { - ($($args:tt)*) => { - stringify!($($args)*) +macro_rules! _log__format_args { + ($($inner:tt)*) => { + format_args! { $($inner)* } } } ``` From 93cbf641f050335fcb95d7742e7041459cdd6521 Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Thu, 25 Oct 2018 16:18:00 +0100 Subject: [PATCH 4/7] Fix errors + add mdbook version to travis. --- .travis.yml | 1 + src/rust-2018/macros/macro-changes.md | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 35cca71e..363fea9a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ before_script: - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) - (test -x $HOME/.cargo/bin/mdbook || cargo install mdbook) - cargo install-update -a + - mdbook --version script: - mdbook build - mdbook test diff --git a/src/rust-2018/macros/macro-changes.md b/src/rust-2018/macros/macro-changes.md index 0d755672..8e224c8b 100644 --- a/src/rust-2018/macros/macro-changes.md +++ b/src/rust-2018/macros/macro-changes.md @@ -89,16 +89,16 @@ supporting both versions of rust more complicated. For example, let's make a simplified (and slightly contrived) version of the `log` crate in 2015 edition style: -```rust,ignore +```rust /// How important/severe the log message is. #[derive(Copy, Clone)] -pub struct LogLevel { +pub enum LogLevel { Warn, Error } impl fmt::Display for LogLevel { - pub fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { LogLevel::Warn => write!(f, "warning"), LogLevel::Error => write!(f, "error"), @@ -159,7 +159,7 @@ which would make our code compile, but `__impl_log` is meant to be an implementa The cleanest way to handle this situation is to use the `$crate::` prefix for macros, the same as you would for any other path. Versions of the compiler >= 1.30 will handle this in both editions: -```rust,ignore +```rust macro_rules! warn { ($($args:tt)*) => { $crate::__impl_log!($crate::LogLevel::Warn, format_args!($($args)*)) @@ -193,7 +193,7 @@ solution is to add a level of indirection: we crate a macro that wraps `format_a to our crate. That way everything works in both editions (sadly we have to pollute the global namespace a bit, but that's ok). -```rust,ignore +```rust // I've used the pattern `___` to name this macro, hopefully avoiding // name clashes. #[doc(hidden)] @@ -211,15 +211,17 @@ whatever tokens we get to the inner macro, and rely on it to report errors. So the full 2015/2018 working example would be: ```rust +use std::fmt; + /// How important/severe the log message is. -#[derive(Copy, Clone)] -pub struct LogLevel { +#[derive(Debug, Copy, Clone)] +pub enum LogLevel { Warn, Error } impl fmt::Display for LogLevel { - pub fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { LogLevel::Warn => write!(f, "warning"), LogLevel::Error => write!(f, "error"), From 5705ef2bd440cc40461984472922ac1c532f4901 Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Thu, 25 Oct 2018 16:24:52 +0100 Subject: [PATCH 5/7] Fix errors --- src/rust-2018/macros/macro-changes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/rust-2018/macros/macro-changes.md b/src/rust-2018/macros/macro-changes.md index 8e224c8b..fc9d7733 100644 --- a/src/rust-2018/macros/macro-changes.md +++ b/src/rust-2018/macros/macro-changes.md @@ -90,6 +90,8 @@ For example, let's make a simplified (and slightly contrived) version of the `lo edition style: ```rust +use std::fmt; + /// How important/severe the log message is. #[derive(Copy, Clone)] pub enum LogLevel { From 6cb392c8731cbf389bfeebca7bd05f210f84a6c5 Mon Sep 17 00:00:00 2001 From: "Richard Dodd (dodj)" Date: Fri, 26 Oct 2018 18:10:51 +0100 Subject: [PATCH 6/7] Fix typo crate -> create --- src/rust-2018/macros/macro-changes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rust-2018/macros/macro-changes.md b/src/rust-2018/macros/macro-changes.md index fc9d7733..f4c97865 100644 --- a/src/rust-2018/macros/macro-changes.md +++ b/src/rust-2018/macros/macro-changes.md @@ -191,7 +191,7 @@ macro_rules! warn { So the code knows to look for any macros used locally. But wait - this won't compile, because we use the `format_args!` macro that isn't in our local crate (hence the convoluted example). The -solution is to add a level of indirection: we crate a macro that wraps `format_args`, but is local +solution is to add a level of indirection: we create a macro that wraps `format_args`, but is local to our crate. That way everything works in both editions (sadly we have to pollute the global namespace a bit, but that's ok). From 820c5ab548b0274160bb3ae7932e9ad6425da865 Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Sat, 10 Nov 2018 15:38:38 +0000 Subject: [PATCH 7/7] Fix typo (use wrong format_args) --- src/rust-2018/macros/macro-changes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rust-2018/macros/macro-changes.md b/src/rust-2018/macros/macro-changes.md index fc9d7733..12033319 100644 --- a/src/rust-2018/macros/macro-changes.md +++ b/src/rust-2018/macros/macro-changes.md @@ -244,7 +244,7 @@ macro_rules! __impl_log { #[macro_export(local_inner_macros)] macro_rules! warn { ($($args:tt)*) => { - __impl_log!($crate::LogLevel::Warn, format_args!($($args)*)) + __impl_log!($crate::LogLevel::Warn, _log__format_args!($($args)*)) } } @@ -252,7 +252,7 @@ macro_rules! warn { #[macro_export(local_inner_macros)] macro_rules! error { ($($args:tt)*) => { - __impl_log!($crate::LogLevel::Error, format_args!($($args)*)) + __impl_log!($crate::LogLevel::Error, _log__format_args!($($args)*)) } }