Max Wang
Max Wang

Reputation: 21

rust macro, convert $expr to different type

Currently I am working on the actor model, and I am using macro to build different messages, such as actor msg and system msg, based on the keyword.

workload to create a dummy workload to mock the computation process, it needs two args, payload: usize, and op: OpCode(OperationType).

create-actor to create actors, two args are count: uszie, and name: String.

Workload and SystemCommand can be converted Into TypedMessage.

#[macro_export]
macro_rules! build_msg {
    ($binary: expr, $arg1:tt, $arg2:expr) => {
        {
            let keyword: &str = $binary;
            match keyword {
                "workload" => {
                    let msg: TypedMessage = Workload::new($arg1 as usize,  $arg2 as OpCode).into();
                    msg
                }
                "create-actor" => {
                    let name: &str = arg2;
                    let msg:TypedMessage = SystemCommand::CreateActor($arg1 as usize, $name.to_owned()).into();
                    msg
                }
                _ => {
                    panic!("Unknow Keyword, or number of vars not match with Keyword");
                }
            }
        }
    };
}

However, I get an error: mismatched types expected struct String, found enum messages::OpCode.

#[test]
    fn macro_build_msg_test() {
        let wl_macro_1: TypedMessage = build_msg!("workload", 2, OpCode::AddOp);  <- Problem here: OpCode::AddOp 
        assert_eq!(wl_macro_1, Workload::new(2, OpCode::AddOp).into());
    }

Based on the keyword and match, it should get into the different branch. So the args should be converted to the corresponding types. Why I get this error? How could I solve it?

Upvotes: 1

Views: 372

Answers (2)

kmdreko
kmdreko

Reputation: 60682

You don't want your macro to create a match with all the possibilities because all arms of the match would have to be valid for all types of inputs. Instead you should generate different code depending on the initial field:

macro_rules! build_msg {
    ("workload", $arg1:expr, $arg2:expr) => { {
        let msg: TypedMessage = Workload::new($arg1, $arg2).into();
        msg
    } };
    ("create-actor", $arg1:expr, $arg2:expr) => { {
        let msg: TypedMessage = SystemCommand::CreateActor($arg1, $arg2.to_owned()).into();
        msg
    } };
}

You can use it just as before: build_msg!("workload", 2, OpCode::AddOp); If the types are incorrect, or the first argument is not literally "workload" or "create-actor", you'll get compile-time error.

Upvotes: 0

hkBst
hkBst

Reputation: 3380

The problem is that, in your code, $arg2:expr has to have two different types at the same time, which is why you get an error saying as much.

You seem to want to combine different commands with their arguments and have tried to exploit some superficial resemblance between workload and create-actor, such as the number of parameters and their type. It is better to not try to rely on this and package a command with its arguments into one thing. You can do this with an enum:

enum Command {
    Workload { payload: usize, op: OperationType },
    CreateActor { count: usize, name: String },
}

Then you can match against the enum variants.

Finally, I don't see why you are trying to use a macro; maybe a normal function will do?

Upvotes: 0

Related Questions