Sunday, March 15, 2015

Cap'n Proto Rust Macros

I've found Rust is pretty good at designing powerfully for the common use case while still smoothly supporting uncommon ones. unsafe is a great example: Rust provides tons of tools to keep yourself from shooting yourself in the foot in ways that are common in C++, like null pointers and pointers to expired data. But if what Rust provides doesn't work for you, for whatever reason (e.g. your data ownership is complicated enough that you can't explain it to the compiler), then you can opt out of these things using unsafe. You, as the programmer, accept the responsibility for the things you've given up.

The macro system is another good example. Macros are pretty effective for a lot of the macro-y use cases: you can use them as inline functions, you can use them to parse varargs, and you can use them to create tree-style DSLs, like this example taken from the Rust guide:

  let mut out = String::new();

  write_html!(&mut out,
      html[
          head[title["Macros guide"]]
          body[h1["Macros are the best!"]]
      ]);

  assert_eq!(out,
      "<html><head><title>Macros guide</title></head>\
       <body><h1>Macros are the best!</h1></body></html>");

I used macros to make a DSL like that for Cap'n Proto message construction code, and I'm pretty happy with the results. This is the example from capnpc-rust:

let mut message = MallocMessageBuilder::new_default();
{
    let address_book = message.init_root::<address_book::Builder>();

    let mut people = address_book.init_people(2);

    {
        let mut alice = people.borrow().get(0);
        alice.set_id(123);
        alice.set_name("Alice");
        alice.set_email("alice@example.com");
        {
            let mut alice_phones = alice.borrow().init_phones(1);
            alice_phones.borrow().get(0).set_number("555-1212");
            alice_phones.borrow().get(0).set_type(person::phone_number::Type::Mobile);
        }
        alice.get_employment().set_school("MIT");
    }

    {
        let mut bob = people.get(1);
        bob.set_id(456);
        bob.set_name("Bob");
        bob.set_email("bob@example.com");
        {
            let mut bob_phones = bob.borrow().init_phones(2);
            bob_phones.borrow().get(0).set_number("555-4567");
            bob_phones.borrow().get(0).set_type(person::phone_number::Type::Home);
            bob_phones.borrow().get(1).set_number("555-7654");
            bob_phones.borrow().get(1).set_type(person::phone_number::Type::Work);
        }
        bob.get_employment().set_unemployed(());
    }
}

And here's how it looks using capnpc-macros:

let mut message =
    capnpc_new!(
        address_book::Builder =>
        [array init_people 2 =>
            [
                [set_id 123]
                [set_name "Alice"]
                [set_email "alice@example.com"]
                [array init_phones 1 =>
                  [
                      [set_number "555-1212"]
                      [set_type {person::phone_number::Type::Mobile}]
                  ]
                ]
                [init_employment => [set_school "MIT"]]
            ]

            [
                [set_id 456]
                [set_name "Bob"]
                [set_email "bob@example.com"]
                [array init_phones 2 =>
                    [
                        [set_number "555-4567"]
                        [set_type {person::phone_number::Type::Home}]
                    ]
                    [
                        [set_number "555-7654"]
                        [set_type {person::phone_number::Type::Work}]
                    ]
                ]
                [init_employment => [set_unemployed ()]]
            ]
        ]
    );
I think that's pretty cool.

No comments:

Post a Comment