Hello! Welcome back to building our chess game in Rust.
In our last lesson, you did a great job setting up the fundamental structure for our game board. We created the Board struct containing an 8x8 grid of Option<Piece>, representing an empty chessboard.
Today, we'll bring that empty board to life. Our goal is to write a function that places all 32 chess pieces in their standard starting positions. This will give us the initial game state, a crucial step towards making our game playable.
1. The Starting Position: Our Target
Before we write any code, let's be crystal clear about what we want to achieve. Here is the standard starting setup for a game of chess:

To translate this image into our [[Option<Piece>; 8]; 8] array, we need to agree on a coordinate system. Let's establish a convention that we'll use throughout the project:
- The array will be indexed as
pieces[rank][file]. - Ranks (rows):
rank 0corresponds to the 8th rank (Black's back rank), andrank 7corresponds to the 1st rank (White's back rank). - Files (columns):
file 0corresponds to the 'a'-file, andfile 7corresponds to the 'h'-file.
So, for example:
- The square a1 is at
pieces[7][0]. - The square h8 is at
pieces[0][7]. - The square e4 is at
pieces[4][4].
2. The "Constructor" Pattern in Rust
We need a function that creates and returns a new Board instance, all set up and ready to go. In object-oriented languages like Java, you might use a constructor for this. Rust doesn't have constructors in the same way, but it has a very common and powerful pattern: associated functions.
An associated function is a function that belongs to a type (like a struct or enum). A common convention is to create a function named new() that returns a new instance of the type.
We can see this pattern suggested in the "Data Structures" resource you've seen before, where a Board::new_empty() function was created.
Let's revisit this resource to see the idiomatic Rust pattern for creating new instances of a struct.
Scroll down to the 'Structures' section. Notice how the initial, verbose struct creation is improved by moving the logic into an impl Board block with an associated function fn new_empty() -> Board. We will use this exact pattern, but our function will create a board with pieces instead of an empty one.
We will create an impl Board block and define a pub fn new() -> Board function. This will be our "constructor" for creating a new, fully initialized chessboard.
3. Hands-On: Initializing the Board
It's time to write the code. We will implement the Board::new() function by manually setting each piece. This approach is straightforward, easy to debug, and directly translates the visual board into code.
-
Open
src/board/mod.rs:
This is where you defined theBoardstruct. We'll add theimplblock right below it. -
Implement
Board::new():
Add the following code to the bottom ofsrc/board/mod.rs. I've included the complete function for you. Read through it carefully and see how the code corresponds to the board image above.// In src/board/mod.rs, after the Board struct definition use crate::pieces::{Piece, PieceColor, PieceType}; impl Board { /// Creates a new Board with pieces in their standard starting positions. pub fn new() -> Board { let mut pieces = [[None; 8]; 8]; // --- Place White Pieces --- // Back Rank (Rank 1) pieces[7][0] = Some(Piece { color: PieceColor::White, kind: PieceType::Rook }); pieces[7][1] = Some(Piece { color: PieceColor::White, kind: PieceType::Knight }); pieces[7][2] = Some(Piece { color: PieceColor::White, kind: PieceType::Bishop }); pieces[7][3] = Some(Piece { color: PieceColor::White, kind: PieceType::Queen }); pieces[7][4] = Some(Piece { color: PieceColor::White, kind: PieceType::King }); pieces[7][5] = Some(Piece { color: PieceColor::White, kind: PieceType::Bishop }); pieces[7][6] = Some(Piece { color: PieceColor::White, kind: PieceType::Knight }); pieces[7][7] = Some(Piece { color: PieceColor::White, kind: PieceType::Rook }); // Pawns (Rank 2) - using a loop to keep it DRY (Don't Repeat Yourself) for file in 0..8 { pieces[6][file] = Some(Piece { color: PieceColor::White, kind: PieceType::Pawn }); } // --- Place Black Pieces --- // Pawns (Rank 7) for file in 0..8 { pieces[1][file] = Some(Piece { color: PieceColor::Black, kind: PieceType::Pawn }); } // Back Rank (Rank 8) pieces[0][0] = Some(Piece { color: PieceColor::Black, kind: PieceType::Rook }); pieces[0][1] = Some(Piece { color: PieceColor::Black, kind: PieceType::Knight }); pieces[0][2] = Some(Piece { color: PieceColor::Black, kind: PieceType::Bishop }); pieces[0][3] = Some(Piece { color: PieceColor::Black, kind: PieceType::Queen }); pieces[0][4] = Some(Piece { color: PieceColor::Black, kind: PieceType::King }); pieces[0][5] = Some(Piece { color: PieceColor::Black, kind: PieceType::Bishop }); pieces[0][6] = Some(Piece { color: PieceColor::Black, kind: PieceType::Knight }); pieces[0][7] = Some(Piece { color: PieceColor::Black, kind: PieceType::Rook }); Board { pieces } } }A quick note: Make sure the
use crate::pieces::{...}line is at the top of yoursrc/board/mod.rsfile. You may have added a similar line in a previous lesson. -
Update
main.rsto use the new function:
Now, let's use our new "constructor". Go to yourmainfunction insrc/main.rs.Replace the line that creates the
empty_boardwith a call toBoard::new().// In the main() function of src/main.rs fn main() { // ... previous code can be removed or commented out ... // Create a new board with pieces in their starting positions. let board = Board::new(); // Print it to see the result! println!("New board with starting pieces: {:?}", board); }Don't forget to keep your
use board::Board;line at the top ofmain.rs! -
Run Your Code:
Executecargo runin your terminal. This time, instead of seeing an 8x8 grid ofNone, you'll see a much more interesting output. The back ranks and pawn ranks will be filled withSome(Piece { ... })structs, while the middle ranks will still beNone. You have successfully represented the start of a chess game!
Conclusion
Fantastic work! You've taken a significant step forward today. You now have a function that can produce a Board struct representing a complete, standard chess game setup.
Key Takeaways:
- A clear coordinate system (
[rank][file]) is essential for mapping between the conceptual board and the data structure. - Associated functions, often named
new(), are the idiomatic Rust pattern for creating new instances of a struct, similar to constructors in other languages. - We can initialize our board by creating a mutable 2D array, populating it with
Some(Piece)values at the correct indices, and then returning it inside aBoardstruct. - Using loops for repetitive setups, like placing the pawns, can make the initialization code cleaner.
The Debug output you see is functional, but it's not very easy to read. In the next lesson, we will implement a method to print the board state to the console in a nicely formatted, human-readable way, which will be invaluable for debugging as we start to implement piece movement.