Update the tutorial with the new macro system.

This commit is contained in:
Paul Stansifer 2012-08-22 19:07:46 -07:00
parent 77e83d83a9
commit 9ef56a6ca8

View file

@ -642,31 +642,31 @@ applies to the module or crate in which it appears.
## Syntax extensions
There are plans to support user-defined syntax (macros) in Rust. This
currently only exists in very limited form.
The compiler defines a few built-in syntax extensions. The most useful
one is `#fmt`, a printf-style text formatting macro that is expanded
one is `fmt!`, a sprintf-style text formatter that is expanded
at compile time.
~~~~
io::println(fmt!("%s is %d", ~"the answer", 42));
~~~~
`#fmt` supports most of the directives that [printf][pf] supports, but
`fmt!` supports most of the directives that [printf][pf] supports, but
will give you a compile-time error when the types of the directives
don't match the types of the arguments.
[pf]: http://en.cppreference.com/w/cpp/io/c/fprintf
All syntax extensions look like `#word`. Another built-in one is
`#env`, which will look up its argument as an environment variable at
All syntax extensions look like `extension_name!`. Another built-in one is
`env!`, which will look up its argument as an environment variable at
compile-time.
~~~~
io::println(env!("PATH"));
~~~~
It is possible for the user to define new syntax extensions, within certain
limits. These are called [macros](#macros).
# Control structures
## Conditionals
@ -902,7 +902,7 @@ So except in code that needs to be really, really fast,
you should feel free to scatter around debug logging statements, and
leave them in.
Three macros that combine text-formatting (as with `#fmt`) and logging
Three macros that combine text-formatting (as with `fmt!`) and logging
are available. These take a string and any number of format arguments,
and will log the formatted string:
@ -912,7 +912,7 @@ warn!("only %d seconds remaining", 10);
error!("fatal: %s", get_error_string());
~~~~
Because the macros `#debug`, `#warn`, and `#error` expand to calls to `log`,
Because the macros `debug!`, `warn!`, and `error!` expand to calls to `log`,
their arguments are also lazily evaluated.
# Functions
@ -2334,6 +2334,151 @@ This makes it possible to rebind a variable without actually mutating
it, which is mostly useful for destructuring (which can rebind, but
not assign).
# Macros
Functions are the programmer's primary tool of abstraction, but there are
cases in which they are insufficient, because the programmer wants to
abstract over concepts not represented as values. Consider the following
example:
~~~~
# enum t { special_a(uint), special_b(uint) };
# fn f() -> uint {
# let input_1 = special_a(0), input_2 = special_a(0);
match input_1 {
special_a(x) => { return x; }
_ => {}
}
// ...
match input_2 {
special_b(x) => { return x; }
_ => {}
}
# return 0u;
# }
~~~~
This code could become tiresome if repeated many times. However, there is
no reasonable function that could be written to solve this problem. In such a
case, it's possible to define a macro to solve the problem. Macros are
lightweight custom syntax extensions, themselves defined using the
`macro_rules!` syntax extension:
~~~~
# enum t { special_a(uint), special_b(uint) };
# fn f() -> uint {
# let input_1 = special_a(0), input_2 = special_a(0);
macro_rules! early_return(
($inp:expr $sp:ident) => ( //invoke it like `(input_5 special_e)`
match $inp {
$sp(x) => { return x; }
_ => {}
}
);
);
// ...
early_return!(input_1 special_a);
// ...
early_return!(input_2 special_b);
# return 0;
# }
~~~~
Macros are defined in pattern-matching style:
## Invocation syntax
On the left-hand-side of the `=>` is the macro invocation syntax. It is
free-form, excepting the following rules:
1. It must be surrounded in parentheses.
2. `$` has special meaning.
3. The `()`s, `[]`s, and `{}`s it contains must balance. For example, `([)` is
forbidden.
To take as an argument a fragment of Rust code, write `$` followed by a name
(for use on the right-hand side), followed by a `:`, followed by the sort of
fragment to match (the most common ones are `ident`, `expr`, `ty`, `pat`, and
`block`). Anything not preceeded by a `$` is taken literally. The standard
rules of tokenization apply,
So `($x:ident => (($e:expr)))`, though excessively fancy, would create a macro
that could be invoked like `my_macro!(i=>(( 2+2 )))`.
## Transcription syntax
The right-hand side of the `=>` follows the same rules as the left-hand side,
except that `$` need only be followed by the name of the syntactic fragment
to transcribe.
## Multiplicity
### Invocation
Going back to the motivating example, suppose that we wanted each invocation
of `early_return` to potentially accept multiple "special" identifiers. The
syntax `$(...)*` accepts zero or more occurences of its contents, much like
the Kleene star operator in regular expressions. It also supports a separator
token (a comma-separated list could be written `$(...),*`), and `+` instead of
`*` to mean "at least one".
~~~~
# enum t { special_a(uint),special_b(uint),special_c(uint),special_d(uint)};
# fn f() -> uint {
# let input_1 = special_a(0), input_2 = special_a(0);
macro_rules! early_return(
($inp:expr, [ $($sp:ident)|+ ]) => (
match $inp {
$(
$sp(x) => { return x; }
)+
_ => {}
}
);
);
// ...
early_return!(input_1, [special_a|special_c|special_d]);
// ...
early_return!(input_2, [special_b]);
# return 0;
# }
~~~~
### Transcription
As the above example demonstrates, `$(...)*` is also valid on the right-hand
side of a macro definition. The behavior of Kleene star in transcription,
especially in cases where multiple stars are nested, and multiple different
names are involved, can seem somewhat magical and intuitive at first. The
system that interprets them is called "Macro By Example". The two rules to
keep in mind are (1) the behavior of `$(...)*` is to walk through one "layer"
of repetitions for all of the `$name`s it contains in lockstep, and (2) each
`$name` must be under at least as many `$(...)*`s as it was matched against.
If it is under more, it'll will be repeated, as appropriate.
## Parsing limitations
The parser used by the macro system is reasonably powerful, but the parsing of
Rust syntax is restricted in two ways:
1. The parser will always parse as much as possible. For example, if the comma
were omitted from the syntax of `early_return!` above, `input_1 [` would've
been interpreted as the beginning of an array index. In fact, invoking the
macro would have been impossible.
2. The parser must have eliminated all ambiguity by the time it reaches a
`$name:fragment_specifier`. This most often affects them when they occur in
the beginning of, or immediately after, a `$(...)*`; requiring a distinctive
token in front can solve the problem.
## A final note
Macros, as currently implemented, are not for the faint of heart. Even
ordinary syntax errors can be more difficult to debug when they occur inside
a macro, and errors caused by parse problems in generated code can be very
tricky. Invoking the `log_syntax!` macro can help elucidate intermediate
states, and using `--pretty expanded` as an argument to the compiler will
show the result of expansion.
# Traits
Traits are Rust's take on value polymorphism—the thing that