Rust by Example — страница 43 из 66

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:

use std::ops::{Add, Mul, Sub};

macro_rules! assert_equal_len {

// The `tt` (token tree) designator is used for

// operators and tokens.

($a:expr, $b:expr, $func:ident, $op:tt) => {

assert!($a.len() == $b.len(),

"{:?}: dimension mismatch: {:?} {:?} {:?}",

stringify!($func),

($a.len(),),

stringify!($op),

($b.len(),));

};

}

macro_rules! op {

($func:ident, $bound:ident, $op:tt, $method:ident) => {

fn $func + Copy>(xs: &mut Vec, ys: &Vec) {

assert_equal_len!(xs, ys, $func, $op);

for (x, y) in xs.iter_mut().zip(ys.iter()) {

*x = $bound::$method(*x, *y);

// *x = x.$method(*y);

}

}

};

}

// Implement `add_assign`, `mul_assign`, and `sub_assign` functions.

op!(add_assign, Add, +=, add);

op!(mul_assign, Mul, *=, mul);

op!(sub_assign, Sub, -=, sub);

mod test {

use std::iter;

macro_rules! test {

($func:ident, $x:expr, $y:expr, $z:expr) => {

#[test]

fn $func() {

for size in 0usize..10 {

let mut x: Vec<_> = iter::repeat($x).take(size).collect();

let y: Vec<_> = iter::repeat($y).take(size).collect();

let z: Vec<_> = iter::repeat($z).take(size).collect();

super::$func(&mut x, &y);

assert_eq!(x, z);

}

}

};

}

// Test `add_assign`, `mul_assign`, and `sub_assign`.

test!(add_assign, 1u32, 2u32, 3u32);

test!(mul_assign, 2u32, 3u32, 6u32);

test!(sub_assign, 3u32, 2u32, 1u32);

}

הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

$ 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

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 concise or intuitive syntax for some special functionality (within bounds).

Suppose that I want to define a little calculator API. I would like to supply an expression and have the output printed to console.

macro_rules! calculate {

(eval $e:expr) => {{

{

let val: usize = $e; // Force types to be integers

println!("{} = {}", stringify!{$e}, val);

}

}};

}

fn main() {

calculate! {

eval 1 + 2 // hehehe `eval` is _not_ a Rust keyword!

}

calculate! {

eval (1 + 2) * (3 / 4)

}

}

הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Output:

1 + 2 = 3

(1 + 2) * (3 / 4) = 0

This was a very simple example, but much more complex interfaces have been developed, such as lazy_static or clap.

Also, note the two pairs of braces in the macro. The outer ones are part of the syntax of macro_rules!, in addition to () or [].

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 section to be variadic:

macro_rules! calculate {

// The pattern for a single `eval`

(eval $e:expr) => {{

{

let val: usize = $e; // Force types to be integers

println!("{} = {}", stringify!{$e}, val);

}

}};

// Decompose multiple `eval`s recursively

(eval $e:expr, $(eval $es:expr),+) => {{

calculate! { eval $e }

calculate! { $(eval $es),+ }

}};

}

fn main() {

calculate! { // Look ma! Variadic `calculate!`!

eval 1 + 2,

eval 3 + 4,

eval (2 * 3) + 1

}

}

הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Output:

1 + 2 = 3

3 + 4 = 7

(2 * 3) + 1 = 7

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 saves the rest of the program from various pitfalls.

There are various ways to deal with errors in Rust, which are described in the following subchapters. They all have more or less subtle differences and different use cases. As a rule of thumb: