Simple enumerations

Rather than integers and magic constants, let’s use convenient enumerations whenever possible. Use the Debug trait to make your life easier.

enum Piece {
    King,
    Queen,
    Bishop,
    Knight,
    Rook,
    Pawn,
}

fn main() {
    let piece = Piece::King;
    println!("{:?}", piece);
}

Tuples

The simplest way to create compound types and values is to design a small heterogenous sequence called a tuple.

#[derive(Debug)]
enum Color {
    White,
    Black,
}

fn main() {
    let king = (Piece::King, Color::White);
    println!("{:?}", king);
}

Enumeration with data

Another way is to use enums for building the so called algebraic types.

#[derive(Debug)]
enum Item {
    White(Piece),
    Black(Piece),
}

fn main() {
    let king = Item::White(Piece::King);
    println!("{:?}", king);
}

Fixed size arrays

Make your types copyable in order to create short fixed size arrays using the repeat syntax.

#[derive(Debug, Clone, Copy)]
enum Piece {
    King,
    Queen,
    Bishop,
    Knight,
    Rook,
    Pawn,
}

fn main() {
    let row = [Piece::King; 8];
    println!("{:?}", row);
}

You can use the same technique for multi-dimensional arrays.

fn main() {
    let row = [Piece::King; 8];
    let board = [row; 8];
    println!("{:?}", board);
}

Now you have a proper chess board full of kings. You can use the previous examples to add support for piece colors as well as empty fields.

Enumeration templates

The library gives you a few common types that make your life easier. The Option type makes a value optional. An optional field is either None or Some(value).

fn main() {
    let mut board = [[None; 8]; 8];
    board[0][0] = Some(Piece::Rook);
    println!("{:?}", board);
}

Hint: Check the type of board[0][0] using the technique from the previous lecture.

Another standard template is Result. It is an idiomatic way to express the result of actions that can fail. A result either returns a valid value or an error.

#[derive(Debug)]
enum Error {
    InvalidRow,
    InvalidColumn,
}

fn replace_field(board: &mut Board, row: usize, col: usize, piece: Piece) -> Result<Piece, Error> {
    if !(0..7).contains(&row) {
        Err(Error::InvalidRow)
    } else if !(0..7).contains(&col) {
        Err(Error::InvalidColumn)
    } else {
        let orig = board[row][col];
        board[row][col] = piece;
        Ok(orig)
    }
}

fn main() {
    let mut board = [[Piece::King; 8]; 8];
    let orig = replace_field(&mut board, 0, 0, Piece::Rook).unwrap();
    println!("{:?}", board);
    println!("{:?}", orig);
}

Pattern matching

Enumerations, tuples, arrays and some other compound data structures can be used for pattern matching. Rust can match not just on exact values but also on structure.

fn describe(item: &Item) {
    let (color, piece) = match item {
        Item::White(piece) => ("white", piece),
        Item::Black(piece) => ("black", piece),
    };
    let piece = match piece {
        Piece::Queen => "queen",
        Piece::King => "king",
        _ => "something",
    };
    println!("This is a {} {}.", color, piece);
}

fn main() {
    let item = Item::Black(Piece::Queen);
    describe(&item);
}

Error handling

In most of our examples we assume all errors are inrecoverable and terminate the program.

fn main() {
    // Just terminate.
    panic!();
}

Failed dynamic memory allocation panics. Any use of .unwrap() or .expect() turns a bad Result or empty Option into panicking.

Proper error handling can be done using pattern matching.

fn main() {
    let mut board = [[Piece::King; 8]; 8];
    match replace_field(&mut board, 0, 0, Piece::Rook) {
        Ok(orig) => {
            println!("{:?}", board);
            println!("{:?}", orig);
        }
        Err(error) => {
            println!("There was an error: {:?}", error);
        }
    }
}

If we’re not interested in the error details, there is a shortcut.

fn main() {
    let mut board = [[Piece::King; 8]; 8];
    if let Ok(orig) = replace_field(&mut board, 0, 0, Piece::Rook) {
            println!("{:?}", board);
            println!("{:?}", orig);
    } else {
        println!("There was an error.");
    }
}

Structures

Using enumerations with tuples is inconvenient. Define your own structured types, use named attributes, associated functions and methods. Decompose the problem domain.

#[derive(Debug, Clone, Copy)]
enum Piece {
    King,
    Queen,
    Bishop,
    Knight,
    Rook,
    Pawn,
}

#[derive(Debug, Clone, Copy)]
enum Color {
    White,
    Black,
}

#[derive(Debug, Clone, Copy)]
struct Item {
    color: Color,
    piece: Piece,
}

#[derive(Debug)]
struct Board {
    fields: [[Option<Item>; 8]; 8],
    current: Color,
}

fn main() {
    let board = Board {
        fields: [[None; 8]; 8],
        current: Color::White,
    };
    println!("{:?}", board);
}

This looks awful. You should be able to create the empty board using a simple function call. Let’s move board implementation details into an associated functions.

impl Board {
    fn new_empty() -> Board {
        Board {
            fields: [[None; 8]; 8],
            current: Color::White,
        }
    }
}

fn main() {
    let board = Board::new_empty();
    println!("{:?}", board);
}

Much better. Let’s make a method that can actually modify the structure. It is an associated function, just with a self mutable reference argument.

impl Board {
    fn new_empty() -> Board {
        Board {
            fields: [[None; 8]; 8],
            current: Color::White,
        }
    }
    fn replace_field(&mut self, row: usize, col: usize, value: Option<Item>) -> Result<Option<Item>, Error> {
        if !(0..7).contains(&row) {
            Err(Error::InvalidRow)
        } else if !(0..7).contains(&col) {
            Err(Error::InvalidColumn)
        } else {
            let orig = self.fields[row][col];
            self.fields[row][col] = value;
            Ok(orig)
        }
    }
}

fn main() {
    let mut board = Board::new_empty();
    match board.replace_field(0, 0, Some(Item { color: Color::Black, piece: Piece::Queen })) {
        Ok(Some(item)) => { println!("We replaced a {:?}.", item); }
        Ok(None) => { println!("We filled an empty field."); }
        Err(error) => { println!("There was an error of type {:?}.", error); }
    }
    println!("{:?}", board);
}

Now you have all the tools to build data structures with associated operations.