Why must I use macros only used by my dependencies

2020-04-23 07:19发布

Why must I explicitly macro_use macros not directly used by my code, but only used by my codes dependencies?

There are 2 cases illustrated below:

  1. Macros only used by my dependencies
    • call, do_parse, map, take, error_if
  2. Other scope names only used by my dependencies
    • parse_der_defined (a function), fold_parsers (a function), DerObject (a struct), DerObjectContent (a struct)

Bonus Question

What's the best workflow to deal with this while coding? Just compiler error, add the name, rinse, repeat?


// Why is this necessary? My code does not directly use macros from this scope.
#[macro_use(call, do_parse, map, take)]
extern crate nom;

// Why is this necessary? My code does not directly use macros from this scope.
#[macro_use(error_if)]
extern crate rusticata_macros;

// Why is this necessary? My code does not directly use macros from this scope.
#[macro_use(parse_der_sequence_defined, parse_der_defined, fold_parsers)]
extern crate der_parser;

// My code does not directly use these names. Why do I need to `use` them?
use der_parser::{der_read_element_header, DerObjectContent};

// Why is this necessary? My code does not directly use these names.
use nom::{Err, ErrorKind};

// I actually use these
use nom::IResult;
use der_parser::DerObject;

fn seq_of_integers(input: &[u8]) -> IResult<&[u8], DerObject> {
    parse_der_sequence_defined!(input, der_parser::parse_der_integer)
}

fn main() {
    let input = [
    0x30, // ASN.1 sequence
    0x03, // length 3 bytes
    0x02, // ASN.1 Integer
    0x01, // length 1 byte
    0x07, // 7
    ];
    let der_result = seq_of_integers(&input);
    match der_result {
        IResult::Done(_unparsed_suffix, der) => {
            assert_eq!(_unparsed_suffix.len(), 0);
            let der_objects = der.as_sequence().unwrap();
            for (index, der_object) in der_objects.iter().enumerate() {
                println!("{}: {}", index, der_object.content.as_u32().unwrap());
            }
        }
        IResult::Error(error) => {
            eprintln!("{}", error);
        }
        IResult::Incomplete(_needed) => {
            eprintln!("{:?}", _needed);
        }
    };
}

标签: rust
1条回答
放我归山
2楼-- · 2020-04-23 07:36

Macros are hygenic, but they don't "bring in" things from the scope they are defined in.

If you define a macro in one crate, and use relative names rather than absolute ones (if the macro produces code using der_read_element_name rather than ::der_parser::der_read_element_name), then you are required to use use to bring those methods into scope.

The solution to this is to always use absolute names when defining macros, or to 'use' them inside the macro scope. For instance, if you have a macro which opened a file, you do one of two things. Either import:

macro_rules! open {
    ($file:expr) => ({
        // note: this has to be inside the macro expansion
        // `::std` means this works anywhere, not just in
        // the crate root where `std` is in scope.
        use ::std::fs::File;

        File::open($file)
    })
}

or use absolute paths directly:

macro_rules! open {
    ($file:expr) => ({
        ::std:fs::File::open($file)
    })
}

A similar thing happens with macros using other macros! If you have two crates, say, cratea with:

macro_rules! say_hello {
    () => (println!("hi"))
}

and crateb with:

#[macro_use]
extern crate cratea;

macro_rules! conversation {
    () => ({
        say_hello!();
        println!("goodbye");
    })
}

then when someone uses crateb with conversation!(), it literally expands to say_hello!(); println!("goodbye");, and this will error if say_hello doesn't exist in the target crate.

The solution to this is to re-export all macros from cratea to crateb. You can do this with the following:

extern crate cratea;
pub use cratea::*;

This will mean anyone who depends using #[macro_use] on crateb will have access to all of cratea's macros too. So, when your macro in crateb expands to reference a macro in cratea, it will work.


On workflow, personal anecdote:

I've found cargo check with cargo-watch to be the best workflow I know of. I'll start cargo watch in a terminal, and whenever a file is saved it will start a check and just report syntax errors.

Once I feel pretty confident and there are no errors, I'll actually run cargo run and/or cargo test depending on the project.

查看更多
登录 后发表回答