Syntax In following subsections, we will show how to define macros in Rust. There are three basic ideas: • Patterns and Designators • Overloading • Repetition
Designators The arguments of a macro are prefixed by a dollar sign $ and type annotated with a designator: macro_rules! create_function { // This macro takes an argument of designator `ident` and // creates a function named `$func_name`. //...
Overload Macros can be overloaded to accept different combinations of arguments. In that regard, macro_rules! can work similarly to a match block: // `test!` will compare `$left` and `$right` // in different ways depending on how you invoke...
Repeat 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. In the following example, surrounding the matcher with $(...),+ will matc...
DRY (Don't Repeat Yourself) 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>: use std::ops::{Add, Mul, Sub};...
Domain Specific Languages (DSLs) A DSL is a mini "language" embedded in a Rust macro. It is completely valid Rust because the macro system expands into normal Rust constructs, but it looks like a small language. This allows you to define co...
Variadic Interfaces A variadic interface takes an arbitrary number of arguments. For example, println! can take an arbitrary number of arguments, as determined by the format string. We can extend our calculate! macro from the previous secti...
Error handling Error handling is the process of handling the possibility of failure. For example, failing to read a file and then continuing to use that bad input would clearly be problematic. Noticing and explicitly managing those errors s...
panic The simplest error handling mechanism we will see is panic. It prints an error message, starts unwinding the stack, and usually exits the program. Here, we explicitly call panic on our error condition: fn drink(beverage: &str) { // Yo...
abort and unwind The previous section illustrates the error handling mechanism panic. Different code paths can be conditionally compiled based on the panic setting. The current values available are unwind and abort. Building on the prior le...
Option & unwrap In the last example, we showed that we can induce program failure at will. We told our program to panic if we drink a sugary lemonade. But what if we expect some drink but don't receive one? That case would be just as bad, s...
Unpacking options with ? You can unpack Options by using match statements, but it's often easier to use the ? operator. If x is an Option, then evaluating x? will return the underlying value if x is Some, otherwise it will terminate whateve...
Combinators: map match is a valid method for handling Options. However, you may eventually find heavy usage tedious, especially with operations only valid with an input. In these cases, combinators can be used to manage control flow in a mo...
Combinators: and_then map() was described as a chainable way to simplify match statements. However, using map() on a function that returns an Option<T> results in the nested Option<Option<T>>. Chaining multiple calls together can then becom...
Unpacking options and defaults There is more than one way to unpack an Option and fall back on a default if it is None. To choose the one that meets our needs, we need to consider the following: • do we need eager or lazy evaluation? • do w...
Result Result is a richer version of the Option type that describes possible error instead of possible absence. That is, Result<T, E> could have one of two outcomes: • Ok(T): An element T was found • Err(E): An error was found with element...
map for Result Panicking in the previous example's multiply does not make for robust code. Generally, we want to return the error to the caller so it can decide what is the right way to respond to errors. We first need to know what kind of...
aliases for Result How about when we want to reuse a specific Result type many times? Recall that Rust allows us to create aliases. Conveniently, we can define one for the specific Result in question. At a module level, creating aliases can...
Early returns In the previous example, we explicitly handled the errors using combinators. Another way to deal with this case analysis is to use a combination of match statements and early returns. That is, we can simply stop executing the...
Introducing ? Sometimes we just want the simplicity of unwrap without the possibility of a panic. Until now, unwrap has forced us to nest deeper and deeper when what we really wanted was to get the variable out. This is exactly the purpose...