How to declare a variable in `macro_rules!`? - rust

I'm creating a macro called throw_error. I expected this to compile yet, it fails:
// Util structs + types
...
// Util macros
#[macro_export]
macro_rules! throw_error {
() => {
RaptorexError {
message: String::new(),
line: line!(),
file: file!().to_owned(),
}
};
($($msg:tt),*) => {
let mut final_msg = String::new();
$(
final_msg.push_str(&format!("{} ", $msg));
)*
// remove trailing whitespace
final_msg.pop();
RaptorexError {
message: final_msg,
line: line!(),
file: file!(),
}
}
}
// Util functions
...
I get several errors from using the macro in my other code.
Erros:
error: macro expansion ignores token `final_msg` and any following
--> /Users/henryboisdequin/Desktop/raptorex/raptorex_util/src/lib.rs:30:13
|
30 | final_msg.push_str(&format!("{} ", $msg));
| ^^^^^^^^^
|
::: compiler/src/parser/parser.rs:44:29
|
44 | _ => return Err(throw_error!("Unexpected token:", current_token)),
| ------------------------------------------------- help: you might be missing a semicolon here: `;`
| |
| caused by the macro expansion here
|
= note: the usage of `throw_error!` is likely invalid in expression context
error[E0658]: `let` expressions in this position are experimental
--> compiler/src/parser/parser.rs:44:29
|
44 | _ => return Err(throw_error!("Unexpected token:", current_token)),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: see issue #53667 <https://github.com/rust-lang/rust/issues/53667> for more information
= help: add `#![feature(let_chains)]` to the crate attributes to enable
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: `let` expressions are not supported here
--> compiler/src/parser/parser.rs:44:29
|
44 | _ => return Err(throw_error!("Unexpected token:", current_token)),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: only supported directly in conditions of `if`- and `while`-expressions
= note: as well as when nested within `&&` and parenthesis in those conditions
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
warning: unused imports: `DATA_TYPES`, `KEYWORDS`
--> compiler/src/parser/parser.rs:3:28
|
3 | lexer::tokens::{Token, DATA_TYPES, KEYWORDS},
| ^^^^^^^^^^ ^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
error[E0308]: mismatched types
--> compiler/src/parser/parser.rs:44:29
|
44 | _ => return Err(throw_error!("Unexpected token:", current_token)),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `raptorex_util::RaptorexError`, found `bool`
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 4 previous errors; 1 warning emitted
What is the cause of these errors and how can I fix it?

You need another set of {}s so the macro creates a block containing the statements instead of the individual statements themselves:
#[macro_export]
macro_rules! throw_error {
() => {
RaptorexError {
message: String::new(),
line: line!(),
file: file!().to_owned(),
}
};
($($msg:tt),*) => {
{ // <------------------
let mut final_msg = String::new();
$(
final_msg.push_str(&format!("{} ", $msg));
)*
// remove trailing whitespace
final_msg.pop();
RaptorexError {
message: final_msg,
line: line!(),
file: file!(),
}
} // <-------------------
}
}
The {} in the (...) => {} is part of the macro syntax and isn't part of the generated code.

Related

Writing tests for nom parsers with VerboseError and convert_error

I'm trying to write tests for nom parsers that provide better messages when they fail.
I have a macro similar to assert_eq! called assert_parses_to! that I want to provide a user-friendly display string when the parser fails:
#[macro_export]
macro_rules! assert_parses_to {
($parser:ident, $input:expr, $expectation:expr) => {
match (&$parser($input), &$expectation) {
(Err(candidate_error), expectation_val) => {
panic!(
"Failed to parse the candidate\n\
{}",
convert_error($input, *candidate_error)
);
}
(Ok(candidate_val), expectation_val) => {
if !(*candidate_val == *expectation_val) {
panic!(
"Failed to parse to expected value\n\
Got: {:?}\n\
Expected: {:?}",
candidate_val, expectation_val
)
}
}
}
};
}
However, when trying to compile, I get this error:
error[E0308]: mismatched types
--> src/parser/tests.rs:27:43
|
27 | convert_error($input, *candidate_error)
| ------------- ^^^^^^^^^^^^^^^^ expected struct `VerboseError`, found enum `nom::Err`
| |
| arguments to this function are incorrect
|
::: src/parser/ansi_2016/lexical.rs:237:9
|
237 | / assert_parses_to!(
238 | | regular_identifier,
239 | | "identifier ",
240 | | (
... |
246 | | )
247 | | )
| |_________- in this macro invocation
|
= note: expected struct `VerboseError<&str>`
found enum `nom::Err<VerboseError<&str>>`
I'm assuming that I need to modify the match statement to extract the VerboseError from the error, but I haven't figured out how.
I'm new to rust and nom - any help appreciated!
Resolved by matching on the inner value of the Err enum:
#[macro_export]
macro_rules! assert_parses_to {
($parser:ident, $input:expr, $expectation:expr) => {
match ($parser($input), $expectation) {
// Deeper match here
(Err(::nom::Err::Error(e) | ::nom::Err::Failure(e)), _) => {
panic!("{}", ::nom::error::convert_error($input, e));
}
(Err(e), _) => {
panic!("Failed to parse {:?}", e)
}
(Ok(candidate_val), expected_val) => {
if !(candidate_val == expected_val) {
panic!(
"Failed to parse to expected value\n\
Got: {:?}\n\
Expected: {:?}",
candidate_val, expected_val
)
}
}
}
};
}

Is there a way to put a type name into a Rust macro?

I have the following code:
trait Trait {
const NAME: &'static str;
const VALUE: i64;
}
struct Struct1 {}
struct Struct2 {}
impl Trait for Struct1 {
const NAME: &'static str = "Aardvark";
const VALUE: i64 = 0;
}
impl Trait for Struct2 {
const NAME: &'static str = "Zebra";
const VALUE: i64 = 100;
}
macro_rules! print_static {
($n:expr, $v:expr) => {
println!("Value of {} is {}", $n, $v);
};
}
macro_rules! print_static2 {
($t:expr) => {
println!("Value of {} is {}", $t::NAME, $t::VALUE);
};
}
fn main() {
print_static!(Struct1::NAME, Struct1::VALUE);
print_static!(Struct2::NAME, Struct2::VALUE);
//print_static2!(Struct1);
//print_static2!(Struct2);
}
It runs as:
Value of Aardvark is 0
Value of Zebra is 100
When I uncomment the print_static2 lines, I get:
error: expected one of `,`, `.`, `?`, or an operator, found `::`
--> src/main.rs:28:41
|
28 | println!("Value of {} is {}", $t::NAME, $t::VALUE);
| ^^ expected one of `,`, `.`, `?`, or an operator
...
37 | print_static2!(Struct1);
| ------------------------ in this macro invocation
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected one of `,`, `.`, `?`, or an operator, found `::`
--> src/main.rs:28:41
|
28 | println!("Value of {} is {}", $t::NAME, $t::VALUE);
| ^^ expected one of `,`, `.`, `?`, or an operator
...
38 | print_static2!(Struct2);
| ------------------------ in this macro invocation
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
Is there a way to specify the type name only once so that I don't end up doing something like this?
// Incorrectly associating Struct2's value with Struct1!
print_static!(Struct1::NAME, Struct2::VALUE);
Rust Playground
If you want to take a type as the parameter, say that:
macro_rules! print_static {
($t:ty) => {
println!("Value of {} is {}", <$t>::NAME, <$t>::VALUE);
};
}
Here's the full list of macro parameter types.

Variable does not need to be mutable, but it does

I have a macro which works:
#[macro_export]
macro_rules! btreemap {
($( $key: expr => $val: expr ),*) => {{
let mut map = ::std::collections::BTreeMap::new();
$( map.insert($key, $val); )*
map
}}
}
but which the compiler warns about:
warning: variable does not need to be mutable
--> src/util.rs:16:11
|
16 | let mut map = ::std::collections::BTreeMap::new();
| ----^^^
| |
| help: remove this `mut`
|
::: src/foo.rs:49:13
|
79 | foo: btreemap!{},
| ----------- in this macro invocation
|
= note: this warning originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
If I remove the mut, I get an error:
error[E0596]: cannot borrow `map` as mutable, as it is not declared as mutable
--> src/util.rs:17:10
|
16 | let map = ::std::collections::BTreeMap::new();
| --- help: consider changing this to be mutable: `mut map`
17 | $( map.insert($key, $val); )*
| ^^^ cannot borrow as mutable
|
::: src/bar.rs:110:18
|
116 | let bar = btreemap!{quux => wibble};
| -------------------------- in this macro invocation
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
i.e. If mut it warns on empty invocations, but if it's not if errors on non-empty invocations.
How can I fix this?
How can I fix this?
You can suppress the warning by adding #[allow(unused_mut)] to the code generated by the macro:
macro_rules! btreemap {
($( $key: expr => $val: expr ),*) => {{
#[allow(unused_mut)]
let mut map = ::std::collections::BTreeMap::new();
$( map.insert($key, $val); )*
map
}}
}
The warning is about as benign as a warning can get, and warning suppressions of this kind are often seen in macro definitions.
Another possibility is the one you already discovered, to special-case empty expansion. But I'd prefer the explicit warning suppression because it eliminates the (bogus) warning without complicating the macro. The special case you introduced wasn't actually necessary - the exact same code would be generated without it.
Fixed by adding a special case for the empty invocation:
#[macro_export]
macro_rules! btreemap {
() => {{
::std::collections::BTreeMap::new()
}};
($( $key: expr => $val: expr ),*) => {{
let mut map = ::std::collections::BTreeMap::new();
$( map.insert($key, $val); )*
map
}};
}

Use paho-mqtt on Rust

I have seen in Crates.io that there is a crate which already implements MQTT. But when trying to use the example provided in the repo I got so many errors and as part of the learning process, I am considering fixing that.
after adding
[dependencies]
paho-mqtt = "0.7.1"
I tried to build using this main.rs:
use std::process;
extern crate paho_mqtt as mqtt;
fn main() {
// Create a client & define connect options
let cli = mqtt::Client::new("tcp://localhost:1883").unwrap_or_else(|err| {
println!("Error creating the client: {:?}", err);
process::exit(1);
});
let conn_opts = mqtt::ConnectOptionsBuilder::new()
.keep_alive_interval(Duration::from_secs(20))
.clean_session(true)
.finalize();
// Connect and wait for it to complete or fail
if let Err(e) = cli.connect(conn_opts).wait() {
println!("Unable to connect:\n\t{:?}", e);
process::exit(1);
}
// Create a message and publish it
let msg = mqtt::Message::new("test", "Hello world!");
let tok = cli.publish(msg);
if let Err(e) = tok.wait() {
println!("Error sending message: {:?}", e);
}
// Disconnect from the broker
let tok = cli.disconnect();
tok.wait().unwrap();
}
I get the following errors:
error[E0433]: failed to resolve: use of undeclared type or module `Duration`
--> src/main.rs:13:30
|
13 | .keep_alive_interval(Duration::from_secs(20))
| ^^^^^^^^ use of undeclared type or module `Duration`
error[E0599]: no method named `wait` found for enum `std::result::Result<mqtt::ServerResponse, mqtt::MqttError>` in the current scope
--> src/main.rs:18:44
|
18 | if let Err(e) = cli.connect(conn_opts).wait() {
| ^^^^ method not found in `std::result::Result<mqtt::ServerResponse, mqtt::MqttError>`
error[E0061]: this function takes 3 arguments but 2 arguments were supplied
--> src/main.rs:24:15
|
24 | let msg = mqtt::Message::new("test", "Hello world!");
| ^^^^^^^^^^^^^^^^^^ ------ -------------- supplied 2 arguments
| |
| expected 3 arguments
error[E0599]: no method named `wait` found for enum `std::result::Result<(), mqtt::MqttError>` in the current scope
--> src/main.rs:27:25
|
27 | if let Err(e) = tok.wait() {
| ^^^^ method not found in `std::result::Result<(), mqtt::MqttError>`
error[E0061]: this function takes 1 argument but 0 arguments were supplied
--> src/main.rs:32:19
|
32 | let tok = cli.disconnect();
| ^^^^^^^^^^- supplied 0 arguments
| |
| expected 1 argument
error[E0599]: no method named `wait` found for enum `std::result::Result<(), mqtt::MqttError>` in the current scope
--> src/main.rs:33:9
|
33 | tok.wait().unwrap();
| ^^^^ method not found in `std::result::Result<(), mqtt::MqttError>`
error: aborting due to 6 previous errors
the error in line 13 I could solve by using std::time::Duration
the error in line 24 I could solve by adding the QoS
What are the other errors that I am missing?

What is the right way to return a simple custom error with nom errorkind?

A simple error handler added to nom
The first one compiles with no errors, the second one errors out
use nom::*;
use std::str;
// This works
named!(
field<&str>,
map!(
complete!(add_return_error!(
ErrorKind::Custom(1),
preceded!(
tag!("."),
recognize!(many1!(
alt!(alphanumeric => { |_| true } | char!('_') => {|_| true})
))
)
)),
|v| str::from_utf8(v).unwrap()
)
);
// This doesn't compile
named!(
entity<&str>,
add_return_error!(
ErrorKind::Custom(2),
map!(
recognize!(many1!(
alt!(alphanumeric => { |_| true } | char!('_') => {|_| true})
)),
|v| str::from_utf8(v).unwrap()
)
)
);
Error is
cargo build
Compiling pdl_parser v0.1.0 (file:///H:/parser)
error[E0282]: type annotations needed
--> src\pdl_parser.rs:72:1
|
72 | / named!(
73 | | entity<&str>,
74 | | add_return_error!(
75 | | ErrorKind::Custom(2),
... |
82 | | )
83 | | );
| |__^ cannot infer type for `E`
|
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
Why does the first one work and second one not? And what can I do to fix it? I tried changing the signature of the second one to entity<&[u8],&str,ErrorKind> and then to i32 and u32 but no success.
The solution to the cannot infer type for E error is to separate out the parsing steps so the compiler does not get so confused.
Rewriting the parser-combinator as
named!(
entity_name<Vec<u8>>,
many1!(alt!(alphanumeric => { |x : &[u8]| x[0] } | char!('_') => {|_| b'_'}))
);
named!(
entity<&[u8],&str,i32>,
add_return_error!(
ErrorKind::Custom(2),
map!(recognize!(entity_name),|v| str::from_utf8(v).unwrap())
)
);
And the corresponding test
#[test]
fn parse_pdl_error_test() {
assert_eq!(entity(b"_error_stack").to_result(), Ok("_error_stack"));
assert_eq!(entity(b"[];']").to_result(), Err(ErrorKind::Custom(2)));
}

Resources