Skip to content

Macros: Split in topics, use playpen and cover more topics #193

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
bin/*
stage/*
*~
41 changes: 41 additions & 0 deletions examples/staging/macros/arguments/designators.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#![feature(macro_rules)]

macro_rules! create_function {
// This macro takes an argument of "type" `ident`
// The `ident` designator is used for variable/function names
($func_name:ident) => {
// This macro creates a function with name `$func_name`
fn $func_name() {
// The `stringify!` macro converts an `ident` into a string
println!("You called {}()", stringify!($func_name))
}
}
}

create_function!(foo)
create_function!(bar)
// Designators provide type safety in macros
//create_function!(0)
// TODO ^ Try uncommenting this line

macro_rules! print_result {
// The `expr` designator is used for expressions
($expression:expr) => {
// `stringify!` will convert the expression *as it is* into a string
println!("{} = {}", stringify!($expression), $expression)
}
}

fn main() {
foo();
bar();

print_result!(1u + 1);

// Remember that blocks are expressions too
print_result!({
let x = 1u;

x * x + 2 * x - 1
});
}
18 changes: 18 additions & 0 deletions examples/staging/macros/arguments/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
The arguments of a macro are prefixed by a dollar sign `$` and type annotated
with a *designator*:

{designators.play}

This is a list of all the designators:

* `block`
* `expr` is used for expressions
* `ident` is used for variable/function names

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's worth adding (*identifier*) here. Same with for expr, pat and stmt. Note: I actually have no idea what stmt is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • stmt (statement) (from IRC)
  • pat (pattern) (not sure though...asked on IRC and got no reply)
  • matchers (lhs of the => in macro rules) (from manual)
  • tt (rhs of the => in macro rules) (from manual; token tree from IRC)

* `item`
* `matchers`
* `pat`
* `path`
* `stmt`
* `tt` (*token tree*) is used for operators (`+`, `-`, etc)
* `ty` (*type*)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does make sense to only explain the common ones but having most of them blank here looks really weird. A brief description might be in order. Also, that Macro by example page still doesn't describe them so linking to it won't be of much help.


15 changes: 15 additions & 0 deletions examples/staging/macros/arity/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Macros can have different implementations depending on their arity (number of
arguments), this can be used to implement some form of function overloading.
Here we implement a macro similar to Python's overloaded
[range](https://docs.python.org/3/library/stdtypes.html?highlight=range#range)
function:

```rust
// Here is how it should work
range!(10i) // Returns 0..9
range!(3i, 10) // Returns 3..9
range!(3i, 10, 2) // Returns 3, 5, 7, 9 (3..9 in increments of 2)
```

{range.play}

32 changes: 32 additions & 0 deletions examples/staging/macros/arity/range.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#![feature(macro_rules)]

use std::iter::range_step;

// `macro_rules` behaves like a `match` expression
macro_rules! range (
// The left side of the fat arrow is the `pattern` side, and the right side
// is the `expansion` side
($end:expr) => {
range(0, $end)
};
// The macro can have a different expansion for each arity
($start:expr, $end:expr) => {
range($start, $end)
};
// ^ Each pattern-match arm must be terminated by a semicolon
($start:expr, $end:expr, $step:expr) => {
range_step($start, $end, $step)
};
// The semicolon on the last arm is optional (is a trailing semicolon)
)

fn main() {
for i in range!(10i) { print!("{}", i) }
println!("");

for i in range!(3i, 10) { print!("{}", i) }
println!("");

for i in range!(3i, 10, 2) { print!("{}", i) }
}

41 changes: 0 additions & 41 deletions examples/staging/macros/designators.rs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![feature(macro_rules)]

macro_rules! assert_equal_len {
// The `tt` (token tree) designator is used for operators and tokens
($a:ident, $b: ident, $func:ident, $op:tt) => {
assert!($a.len() == $b.len(),
"{}: dimension mismatch: {} {} {}",
Expand Down Expand Up @@ -59,3 +60,4 @@ mod test {
test!(mul_assign, 2u, 3u, 6u)
test!(sub_assign, 3u, 2u, 1u)
}

16 changes: 16 additions & 0 deletions examples/staging/macros/dry/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Macros allow writing DRY code, by factoring out the common parts of functions
and/or test suites. Here is an example that implements and tests the `+=`, `*=`
and `-=` operators on `Vec<T>`.

{dry.play}

```
$ rustc --test dry.rs && ./dry
running 3 tests
test test::mul_assign ... ok
test test::add_assign ... ok
test test::sub_assign ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured
```

30 changes: 30 additions & 0 deletions examples/staging/macros/expand/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
You can check the expansion of any macro by running the
`rustc --pretty expanded` command over your source file.

Let's see what the `vec!` macro expands to:

{vec.rs}

``` rust
$ rustc --pretty expanded vec.rs
#![feature(phase)]
#![no_std]
#![feature(globs)]
#[phase(plugin, link)]
extern crate std;
extern crate native;
use std::prelude::*;
fn main() {
let v1 =
{ let mut _temp = ::std::vec::Vec::new(); _temp.push(1u); _temp };
let v2 =
{
let mut _temp = ::std::vec::Vec::new();
_temp.push(1u);
_temp.push(2);
_temp
};
}
```

Soon you'll learn how to define macros that work like this one!
4 changes: 4 additions & 0 deletions examples/staging/macros/expand/vec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
fn main() {
let v1 = vec!(1u);
let v2 = vec!(1u, 2);
}
44 changes: 2 additions & 42 deletions examples/staging/macros/input.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,7 @@ seen in previous chapters, macros look like functions, except that their name
ends with a bang `!`, but instead of generating a function call, macros are
expanded into source code that gets compiled with the rest of the program.

Macros are created using the `macro_rules!` macro.
Macros are defined using the `macro_rules!` macro.

{simple.rs}
{simple.play}

{simple.out}

The arguments of a macro are prefixed by a dollar sign `$` and type annotated
with a *designator*.

{designators.rs}

{designators.out}

Macros can be overloaded to accept different combinations of arguments.

{overload.rs}

{overload.out}

Macros can use `+` in the argument list, to indicate that an argument may
repeat at least once, or `*`, to indicate that the argument may repeat zero or
more times.

{repeat.rs}

{repeat.out}

Macros allow writing DRY code, by factoring out the common parts of functions
and/or test suites. Here is an example that implements and tests the `+=`, `*=`
and `-=` operators on `Vec<T>`.

{dry.rs}

{dry.out}

```
$ rustc --test dry.rs && ./dry
running 3 tests
test test::mul_assign ... ok
test test::add_assign ... ok
test test::sub_assign ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured
```
25 changes: 0 additions & 25 deletions examples/staging/macros/overload.rs

This file was deleted.

32 changes: 32 additions & 0 deletions examples/staging/macros/repetition/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
Macros can be defined to handle indefinite arity (any number of arguments).
This can be accomplished using the *repetition* syntax and the `+`
and `*` operators. This is what the syntax looks like:

On the pattern side:

* `($($x:expr),+)`: matches a list (with at least one element) of `expr`s
separated by commas. Examples: `('a', 'b')` and `(1 + 2, 3 * 4, 5 - 6)`

* `($($y:ident),*)`: matches a list of `ident`s separated by commas, but also
handles the zero arity case. Examples: `()` and `(foo, bar)`

* Neither of these two patterns will match a list that has a trailing comma. To
allow trailing commas, you can use this pattern: `($($z:expr),+,)`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple things are odd if you've used regexes before:

  • $() as a grouping operator is usually optional so as a reader, I want to try and see if I can make it work without it
  • ($($z:expr),+,)
    • the first , seems to check that the item separator matches ,
    • the second , checks for , at the end of the sequence
    • so these are different operators and seem weird that there is no distinction
    • + applies to both $(...) and , which is weird. + usually only applies to the previous character.
    • Surprising interaction with operators:
      • $($x:expr),+ // Works
      • $($x:expr),,+ // Doesn't
      • $($x:expr);+ // Doesn't
      • $($x:expr);;+ // Doesn't
      • $($x:expr)and+ // Works. Surprising that it allows multiple characters if ,, isn't allowed
      • $($x:expr) and + // Works. Allowing whitespace is nice.
      • $($x:expr)|+ // Works but has surprising result
      • $($x:expr)++ // Doesn't work (can operators be escaped?)
      • $($x:expr)$+ // Doesn't work (can operators be escaped?)

I always end up modifying these examples and getting puzzling results so I think expanding the examples would be helpful. I have a PR I'll point to in a little bit to show you what I'm thinking but it's kinda long and perhaps out of place on this page...

[EDIT]
Here it is: #200


On the expansion side:

* `[$($x),+]`: will expand the input arguments (the `$x`s) into an array.
Following the previous example: `['a', 'b']` and `[1 + 2, 3 * 4, 5 - 6]`

For our example, we'll make a `min!` macro that can take any number of
arguments and will return the smallest one. Under the hood it will use the
`min` function that compares *two* arguments.

{repeat.play}

If you check the expansion you'll see this expression in the place of the last
macro:

``` rust
std::cmp::min(5u, std::cmp::min(2u * 3, 4u))
```
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
#![feature(macro_rules)]

// min! will calculate the minimum of any number of arguments
// It's common to use recursion to handle indefinite arity
macro_rules! min {
// base case
($x:expr) => {
$x
};
// `$x` followed by at least one `$y,`
// `$x` followed by at least another `expr`
($x:expr, $($y:expr),+) => {
// call min! on the tail `$y`
// Recursion! Call `min!` on the tail
std::cmp::min($x, min!($($y),+))
}
};
}

fn main() {
Expand Down
20 changes: 20 additions & 0 deletions examples/staging/macros/separators/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
So far we have been using a comma to separate the arguments fed into a macro,
but macros are more flexible than that! You are free to use symbols or words to
separate the input arguments of the macro.

Let's create a sugary macro to initialize hash maps:

{separators.play}

The expansion of the `map!` macro looks like this:

``` rust
let alphabet =
{
let mut _temp = std::collections::HashMap::new();
_temp.insert('a', "apple");
_temp.insert('b', "banana");
_temp.insert('c', "carrot");
_temp
};
```
26 changes: 26 additions & 0 deletions examples/staging/macros/separators/separators.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#![feature(macro_rules)]

macro_rules! map {
($($key:expr => $value:expr),*) => ({
let mut _temp = std::collections::HashMap::new();

$(_temp.insert($key, $value);)*

_temp
});
// Handle trailing commas
($($key:expr => $value:expr),+,) => (
map!($($key => $value),+)
);
}

fn main() {
let alphabet = map! {
'a' => "apple",
'b' => "banana",
'c' => "carrot",
};

println!("{}", alphabet);
}

Loading