Macros in Rust

Steven Fackler / sfackler

Overview

  • What's a macro?
  • Why use macros?
  • What's this externally loadable thing?
  • How does it work?

What's a macro?

This stuff:


let count = try!(r.read(buf));

let foo = bytes!("hello");

include_file!("some_file.rs");

println!("Hello, {}", "world");

#[deriving(Show, Eq)]
struct Foo {
    i: int
}
                        

Not quite like C macros

C's preprocessor operates on a lexical level.

Think find and replace - like sed.


#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define struct union
#define DO_A_THING(t) do { \
        do_blah(t); \
        do_foo(t); \
    } while(0)
                        

Rust's macros operate on the AST

“An abstract syntax tree (AST), or just syntax tree, is a tree representation of the abstract syntactic structure of source code written in a programming language.”

pub enum Expr_ {
    ExprCall(@Expr, ~[@Expr]),
    ExprMatch(@Expr, ~[Arm]),
    ExprForLoop(@Pat, @Expr, P<Block>, Option<Ident>),
    ...
}

pub enum Item_ {
    ItemFn(P<FnDecl>, Purity, AbiSet, Generics, P<Block>),
    ItemTrait(Generics, ~[TraitDef], ~[TraitMethod]),
    ...
}

...
                        

Why?

Macros can do things that normal code simply can't.

Why?

Return from the invoking function


macro_rules! try(
    ($e:expr) => (
        match $e {
            Ok(e) => e,
            Err(e) => return Err(e)
        }
    )
)

fn read_foo(r: &mut Reader) -> Result<Foo, IoError> {
    let bar = try!(r.read_be_i32());
    let baz = try!(r.read_bytes(10));
    Ok(Foo { bar: bar, baz: baz })
}
                        

Why?

(Almost) freeform syntax


let map = make_map!(Start with collections::HashMap::new() and add
                    "foo" => 1,
                    "bar" => 2,
                    "baz" => 3);
                            

Why?

Do work at compile time instead of run time!


test.rs:2:35: 2:37 error: argument never used
test.rs:2     println!("hello {}", "world", 10);
                                            ^~
                        

How does it work?

Syntax extensions don't have to be defined in the compiler anymore! (#11151)

They can be implemented in libraries that the compiler can load into itself.

Basic idea

It's simple for macro_rules!.


#[macro_export]
macro_rules! my_macro(() => (1))
                        

Basic idea

It's a bit more complicated for procedural macros. Libraries define a macro_registrar function to register procedural macros with the compiler.


#[macro_registrar]
pub fn registrar(cb: |ast::Name, ext::base::SyntaxExtension|) {
    ...
}
                        

pub enum SyntaxExtension {
    // e.g. #[deriving(...)]
    ItemDecorator(...),
    // e.g. try!(...)
    NormalTT(...),
    // e.g. macro_rules! foo(...)
    IdentTT(...),
}
                        

Basic idea

Client code annotates the crate import to tell the compiler to load syntax extensions from it.


#[feature(phase)];

#[phase(syntax)]
extern crate my_macros;

fn main() {
    let _x = my_macro!();
}
                        

Example!

Let's write a syntax extension that creates sorted arrays of strings:


#[feature(phase)];

#[phase(syntax)]
extern crate sort;

fn main() {
    assert_eq!(sort!("z", "", "hello", "a"),
               ["", "a", "hello", "z"]);
}
                        

Example!

Source here

Caveats

  • Working with the AST can be extraordinarily verbose - try creating an impl block via AST nodes (or don't).
  • No type information.
  • Namespacing can be tricky. There's no way of knowing what path a certain item is accessible by.

⚠ Warning! ⚠

External syntax extensions are feature gated for a reason! The API can and will change.

Questions?