Skip to content
Merged
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
12 changes: 12 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,18 @@
"higher-order functions"
]
},
{
"uuid": "29583cc6-d56d-4bee-847d-93d74e5a30e7",
"slug": "macros",
"core": false,
"unlocked_by": null,
"difficulty": 4,
"topics": [
"macros",
"macros-by-example",
"hashmap"
]
},
{
"uuid": "94f040d6-3f41-4950-8fe6-acf0945ac83d",
"slug": "allergies",
Expand Down
3 changes: 3 additions & 0 deletions exercises/macros/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Ignore Cargo.lock if creating a library
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
Cargo.lock
29 changes: 29 additions & 0 deletions exercises/macros/.meta/description.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Macros are a powerful part of a Rust programmer's toolkit, and [macros by example](https://doc.rust-lang.org/reference/macros-by-example.html) are a relatively simple way to access this power. Let's write one!

## Context

What is a macro? [Wikipedia](https://en.wikipedia.org/wiki/Macro_(computer_science)) describes it thus:

> A macro (short for "macroinstruction", from Greek μακρός 'long') in computer science is a rule or pattern that specifies how a certain input sequence (often a sequence of characters) should be mapped to a replacement output sequence (also often a sequence of characters) according to a defined procedure. The mapping process that instantiates (transforms) a macro use into a specific sequence is known as macro expansion.

Illuminating! But to be more concrete, macros are a special syntax which allows you to generate code at compile time. Macros can be used compile-time calculation, but more often they're just another way to abstract your code. For example, you've probably already used `println!()` and `vec![]`. These each take an arbitrary number of arguments, so you can't express them as simple functions. On the other hand, they always expand to some amount of absolutely standard Rust code. If you're interested, you can use the [cargo expand](https://github.com/dtolnay/cargo-expand) subcommand to view the results of macro expansion in your code.

For further information about macros in Rust, The Rust Book has a [good chapter](https://doc.rust-lang.org/book/first-edition/macros.html) on them.

## Problem Statement

You can produce a `Vec` of arbitrary length inline by using the `vec![]` macro. However, Rust doesn't come with a way to produce a [`HashMap`](https://doc.rust-lang.org/std/collections/struct.HashMap.html) inline. Rectify this by writing a `hashmap!()` macro.

For example, a user of your library might write `hashmap!('a' => 3, 'b' => 11, 'z' => 32)`. This should expand to the following code:

```rust
{
let mut hm = HashMap::new();
hm.insert('a', 3);
hm.insert('b', 11);
hm.insert('z', 32);
hm
}
```

Note that the [`maplit` crate](https://crates.io/crates/maplit) provides a macro which perfectly solves this exercise. Please implement your own solution instead of using this crate; please make an attempt on your own before viewing its source.
3 changes: 3 additions & 0 deletions exercises/macros/.meta/metadata.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
blurb: "Implement a macro using macros-by-example"
source: "Peter Goodspeed-Niklaus"
6 changes: 6 additions & 0 deletions exercises/macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "macros"
version = "0.1.0"
authors = ["Peter Goodspeed-Niklaus <peter.r.goodspeedniklaus@gmail.com>"]

[dependencies]
70 changes: 70 additions & 0 deletions exercises/macros/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Macros

Macros are a powerful part of a Rust programmer's toolkit, and [macros by example](https://doc.rust-lang.org/reference/macros-by-example.html) are a relatively simple way to access this power. Let's write one!

## Context

What is a macro? [Wikipedia](https://en.wikipedia.org/wiki/Macro_(computer_science)) describes it thus:

> A macro (short for "macroinstruction", from Greek μακρός 'long') in computer science is a rule or pattern that specifies how a certain input sequence (often a sequence of characters) should be mapped to a replacement output sequence (also often a sequence of characters) according to a defined procedure. The mapping process that instantiates (transforms) a macro use into a specific sequence is known as macro expansion.

Illuminating! But to be more concrete, macros are a special syntax which allows you to generate code at compile time. Macros can be used compile-time calculation, but more often they're just another way to abstract your code. For example, you've probably already used `println!()` and `vec![]`. These each take an arbitrary number of arguments, so you can't express them as simple functions. On the other hand, they always expand to some amount of absolutely standard Rust code. If you're interested, you can use the [cargo expand](https://github.com/dtolnay/cargo-expand) subcommand to view the results of macro expansion in your code.

For further information about macros in Rust, The Rust Book has a [good chapter](https://doc.rust-lang.org/book/first-edition/macros.html) on them.

## Problem Statement

You can produce a `Vec` of arbitrary length inline by using the `vec![]` macro. However, Rust doesn't come with a way to produce a [`HashMap`](https://doc.rust-lang.org/std/collections/struct.HashMap.html) inline. Rectify this by writing a `hashmap!()` macro.

For example, a user of your library might write `hashmap!('a' => 3, 'b' => 11, 'z' => 32)`. This should expand to the following code:

```rust
{
let mut hm = HashMap::new();
hm.insert('a', 3);
hm.insert('b', 11);
hm.insert('z', 32);
hm
}
```

Note that the [`maplit` crate](https://crates.io/crates/maplit) provides a macro which perfectly solves this exercise. Please implement your own solution instead of using this crate; please make an attempt on your own before viewing its source.

## Rust Installation

Refer to the [exercism help page][help-page] for Rust installation and learning
resources.

## Writing the Code

Execute the tests with:

```bash
$ cargo test
```

All but the first test have been ignored. After you get the first test to
pass, remove the ignore flag (`#[ignore]`) from the next test and get the tests
to pass again. The test file is located in the `tests` directory. You can
also remove the ignore flag from all the tests to get them to run all at once
if you wish.

Make sure to read the [Modules](https://doc.rust-lang.org/book/second-edition/ch07-00-modules.html) chapter if you
haven't already, it will help you with organizing your files.

## Feedback, Issues, Pull Requests

The [exercism/rust](https://github.com/exercism/rust) repository on GitHub is the home for all of the Rust exercises. If you have feedback about an exercise, or want to help implement new exercises, head over there and create an issue. Members of the [rust track team](https://github.com/orgs/exercism/teams/rust) are happy to help!

If you want to know more about Exercism, take a look at the [contribution guide](https://github.com/exercism/docs/blob/master/contributing-to-language-tracks/README.md).

[help-page]: http://exercism.io/languages/rust
[modules]: https://doc.rust-lang.org/book/second-edition/ch07-00-modules.html
[cargo]: https://doc.rust-lang.org/book/second-edition/ch14-00-more-about-cargo.html

## Source

Peter Goodspeed-Niklaus

## Submitting Incomplete Solutions
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
16 changes: 16 additions & 0 deletions exercises/macros/example.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Ignoring the README's injunction, this is heavily based on the implementation from maplit.
// Original source is at https://github.com/bluss/maplit/blob/master/src/lib.rs#L27-L60

#[macro_export]
macro_rules! hashmap {
($($key:expr => $value:expr,)+) => { hashmap!($($key => $value),+) };
($($key:expr => $value:expr),*) => {
{
let mut _map = ::std::collections::HashMap::new();
$(
_map.insert($key, $value);
)*
_map
}
};
}
6 changes: 6 additions & 0 deletions exercises/macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#[macro_export]
macro_rules! hashmap {
() => {
unimplemented!()
};
}
62 changes: 62 additions & 0 deletions exercises/macros/tests/macros.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#[macro_use]
extern crate macros;

use std::collections::HashMap;

#[test]
fn test_empty() {
let expected: HashMap<usize, usize> = HashMap::new();
let computed: HashMap<usize, usize> = hashmap!();
assert_eq!(computed, expected);
}

#[test]
#[ignore]
fn test_no_trailing_comma() {
let mut expected = HashMap::new();
expected.insert(1, "one");
expected.insert(2, "two");
assert_eq!(hashmap!(1 => "one", 2 => "two"), expected);
}

#[test]
#[ignore]
fn test_trailing_comma() {
let mut expected = HashMap::new();
expected.insert('h', 89);
expected.insert('a', 1);
expected.insert('s', 19);
expected.insert('h', 8);
assert_eq!(
hashmap!(
'h' => 89,
'a' => 1,
's' => 19,
'h' => 8,
),
expected
);
}

#[test]
#[ignore]
fn test_nested() {
let mut expected = HashMap::new();
expected.insert("non-empty", {
let mut subhashmap = HashMap::new();
subhashmap.insert(23, 623);
subhashmap.insert(34, 21);
subhashmap
});
expected.insert("empty", HashMap::new());
assert_eq!(
hashmap!(
"non-empty" => hashmap!(
23 => 623,
34 => 21
),
"empty" => hashmap!()
),
expected
);
}