My first Rust macro
============================================================

Last night I wrote a Rust macro for the first time!! The most striking thing to me about this was how **easy** it was – I kind of expected it to be a weird hard finicky thing, and instead I found that I could go from “I don’t know how macros work but I think I could do this with a macro” to “wow I’m done” in less than an hour.

I used [these examples][2] to figure out how to write my macro.

### what’s a macro?

There’s more than one kind of macro in Rust –

*   macros defined using `macro_rules` (they have an exclamation mark and you call them like functions – `my_macro!()`)

*   “syntax extensions” / “procedural macros” like `#[derive(Debug)]` (you put these like annotations on your functions)

*   built-in macros like `println!`

[Macros in Rust][3] and [Macros in Rust part II][4] seems like a nice overview of the different kinds with examples

I’m not actually going to try to explain what a macro **is**, instead I will just show you what I used a macro for yesterday and hopefully that will be interesting. I’m going to be talking about `macro_rules!`, I don’t understand syntax extension/procedural macros yet.

### compiling the `get_stack_trace` function for 30 different Ruby versions

I’d written some functions that got the stack trace out of a running Ruby program (`get_stack_trace`). But the function I wrote only worked for Ruby 2.2.0 – here’s what it looked like. Basically it imported some structs from `bindings::ruby_2_2_0` and then used them.

```
use bindings::ruby_2_2_0::{rb_control_frame_struct, rb_thread_t, RString};
fn get_stack_trace(pid: pid_t) -> Vec<String> {
    // some code using rb_control_frame_struct, rb_thread_t, RString
}

```

Let’s say I wanted to instead have a version of `get_stack_trace` that worked for Ruby 2.1.6. `bindings::ruby_2_2_0` and `bindings::ruby_2_1_6` had basically all the same structs in them. But `bindings::ruby_2_1_6::rb_thread_t` wasn’t the **same** as `bindings::ruby_2_2_0::rb_thread_t`, it just had the same name and most of the same struct members.

So I could implement a working function for Ruby 2.1.6 really easily! I just need to basically replace `2_2_0` for `2_1_6`, and then the compiler would generate different code (because `rb_thread_t` is different). Here’s a sketch of what the Ruby 2.1.6 version would look like:

```
use bindings::ruby_2_1_6::{rb_control_frame_struct, rb_thread_t, RString};
fn get_stack_trace(pid: pid_t) -> Vec<String> {
    // some code using rb_control_frame_struct, rb_thread_t, RString
}

```

### what I wanted to do

I basically wanted to write code like this, to generate a `get_stack_trace` function for every Ruby version. The code inside `get_stack_trace` would be the same in every case, it’s just the `use bindings::ruby_2_1_3` that needed to be different

```
pub mod ruby_2_1_3 {
    use bindings::ruby_2_1_3::{rb_control_frame_struct, rb_thread_t, RString};
    fn get_stack_trace(pid: pid_t) -> Vec<String> {
        // insert code here
    }
}
pub mod ruby_2_1_4 {
    use bindings::ruby_2_1_4::{rb_control_frame_struct, rb_thread_t, RString};
    fn get_stack_trace(pid: pid_t) -> Vec<String> {
        // same code
    }
}
pub mod ruby_2_1_5 {
    use bindings::ruby_2_1_5::{rb_control_frame_struct, rb_thread_t, RString};
    fn get_stack_trace(pid: pid_t) -> Vec<String> {
        // same code
    }
}
pub mod ruby_2_1_6 {
    use bindings::ruby_2_1_6::{rb_control_frame_struct, rb_thread_t, RString};
    fn get_stack_trace(pid: pid_t) -> Vec<String> {
        // same code
    }
}

```

### macros to the rescue!

This really repetitive thing was I wanted to do was a GREAT fit for macros. Here’s what using `macro_rules!` to do this looked like!

```
macro_rules! ruby_bindings(
    ($ruby_version:ident) => (
    pub mod $ruby_version {
        use bindings::$ruby_version::{rb_control_frame_struct, rb_thread_t, RString};
        fn get_stack_trace(pid: pid_t) -> Vec<String> {
            // insert code here
        }
    }
));

```

I basically just needed to put my code in and insert `$ruby_version` in the places I wanted it to go in. So simple! I literally just looked at an example, tried the first thing I thought would work, and it worked pretty much right away.

(the [actual code][5] is more lines and messier but the usage of macros is exactly as simple in this example)

I was SO HAPPY about this because I’d been worried getting this to work would be hard but instead it was so easy!!

### dispatching to the right code

Then I wrote some super simple dispatch code to call the right code depending on which Ruby version was running!

```
    let version = get_api_version(pid);
    let stack_trace_function = match version.as_ref() {
        "2.1.1" => stack_trace::ruby_2_1_1::get_stack_trace,
        "2.1.2" => stack_trace::ruby_2_1_2::get_stack_trace,
        "2.1.3" => stack_trace::ruby_2_1_3::get_stack_trace,
        "2.1.4" => stack_trace::ruby_2_1_4::get_stack_trace,
        "2.1.5" => stack_trace::ruby_2_1_5::get_stack_trace,
        "2.1.6" => stack_trace::ruby_2_1_6::get_stack_trace,
        "2.1.7" => stack_trace::ruby_2_1_7::get_stack_trace,
        "2.1.8" => stack_trace::ruby_2_1_8::get_stack_trace,
        // and like 20 more versions
        _ => panic!("OH NO OH NO OH NO"),
    };

```

### it works!

I tried out my prototype, and it totally worked! The same program could get stack traces out the running Ruby program for all of the ~10 different Ruby versions I tried – it figured which Ruby version was running, called the right code, and got me stack traces!!

Previously I’d compile a version for Ruby 2.2.0 but then if I tried to use it for any other Ruby version it would crash, so this was a huge improvement.

There are still more issues with this approach that I need to sort out. The two main ones right now are: firstly the ruby binary that ships with Debian doesn’t have symbols and I need the address of the current thread, and secondly it’s still possible that `#ifdefs` will ruin my day.

--------------------------------------------------------------------------------

via: https://jvns.ca/blog/2017/12/24/my-first-rust-macro/

作者:[Julia Evans ][a]
译者:[译者ID](https://github.com/译者ID)
校对:[校对者ID](https://github.com/校对者ID)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出

[a]:https://jvns.ca
[1]:https://jvns.ca/categories/ruby-profiler
[2]:https://gist.github.com/jfager/5936197
[3]:https://www.ncameron.org/blog/macros-in-rust-pt1/
[4]:https://www.ncameron.org/blog/macros-in-rust-pt2/
[5]:https://github.com/jvns/ruby-stacktrace/blob/b0b92863564e54da59ea7f066aff5bb0d92a4968/src/lib.rs#L249-L393