简体   繁体   中英

How to use ty in a Rust macro

I'm try to compose a generic solution to provide fixtures for unit testing Rust code. I have come up with a macro, which allows the user to define setup and teardown methods. Here is my solution so far:

struct FooTestFixture {
    pub name : String
}

impl FooTestFixture {
    fn setup() -> FooTestFixture {
        FooTestFixture { name: String::from("Initialised") }
    }
}

fn teardown(fixture : &mut FooTestFixture) {
    fixture.name = "".to_string();
}

macro_rules! unit_test {
    ($name:ident $fixt:ident $expr:expr) => (
        #[test]
        fn $name() {
            let mut $fixt : FooTestFixture = FooTestFixture::setup();
            $expr;

            teardown(&mut $fixt);
        }
    )
}

unit_test! (heap_foo_fixture_should_be_initialised_using_macro f {
    assert_eq!(f.name, "Initialised");
});

This works. The only problem is, that the macro unit_test is not generic, and is bound to the fixture name FooTestFixture . This means that each test module needs to redefine this macro for every test fixture, which is not ideal. What I'd like to be able to do is to also introduce a type variable and use that type in the macro expansion. Delving more into macros I have found that there is a 'ty' item, that represents a type, and I thought I could do this ...

macro_rules! unit_test {
    ($name:ident $fixt:ident $ftype:ty $expr:expr) => (
        #[test]
        fn $name() {
            let mut $fixt : $ftype = $ftype::setup();
            $expr;

            teardown(&mut $fixt);
        }
    )
}

unit_test! (heap_foo_fixture_should_be_initialised_using_macro FooTestFixture f {
    assert_eq!(f.name, "Initialised");
});

However, this doesn't work and results in the following error:

src\\tests\\heap_fixture_with_new.rs:48:40: 48:50 error: $ftype:ty is followed by $expr:expr , which is not allowed for ty fragments src\\tests\\heap_fixture_with_new.rs:48 ($name:ident $fixt:ident $ftype:ty $expr:expr) => (

As you can see, in the macro definition, I have replaced references to FooTestFixture with $ftype.

Is what I'm trying to achieve possible? It's almost like I'd like the macro to be generic, allowing you to pass in a type, to be used inside the macro definition.

A ty cannot be directly followed by an expr . It must be followed by a specific set of tokens :

  • =>
  • ,
  • =
  • |
  • ;
  • :
  • >
  • [
  • {
  • as
  • where

Similar restriction exists after an expr , stmt , path and pat . This was introduced in RFC 550 to future-proof potential change in Rust syntax .

To fix it you need to change your macro's pattern, eg

macro_rules! unit_test {
    ($name:ident $fixt:ident<$ftype:ty> $expr:expr) => (
//                          ^         ^ followed by '>' is OK

unit_test! (test_name fixture_name<FooTestFixture> f {    
//                                ^              ^

Well I realised I didn't need ty after all. I can just specify the type as an ident parameter so the following does work:

macro_rules! unit_test {
    ($name:ident $fixt:ident $ftype:ident $expr:expr) => (
        #[test]
        fn $name() {
            let mut $fixt = $ftype::setup();
            $expr;

            teardown(&mut $fixt);
        }
    )
}

unit_test! (foo_fixture_should_be_initialised_using_generic_macro f FooTestFixture {
    assert_eq!(f.name, "Initialised");
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM