Game of Life | Rust

Game of Life in Rust

A beginner’s view of using Rust for the first time

Rust logo
Rust logo
source: Photo on rust-lang.org (CC-BY).

Introduction

Rust is an uprising programming language that is loved by the community. The main reason for the upsurge of Rust in recent times is that the language is built for performance and concurrency with safety in mind. At present, the popular use case of Rust is WebAssembly.

So, I wanted to get a gist of the language. The best way(according to me) to learn is to recreate something in the new programming language, which you have already done in a familiar programming language. Hence, I went on to program a simple version of Conway’s Game of Life.

Conway’s Game of Life

Image for post
Image for post
source: Photo on Wikipedia

Game of Life is a cellular automaton proposed by the mathematician John Horton Conway. The universe of Game of Life is an infinitely large plane with square cells, which are either in two states alive, or dead, bound by certain rules.

  • A live cell with less than two live neighbors dies.
  • A live cell with more than three live neighbors dies.
  • A dead cell with exactly three neighbors becomes alive.
  • A live cell with two or three neighbors continues to live.

For more information on Game of Life visit this link. To visualize and play with Game of Life you can visit my web application.

Getting Rust

The first step is to install Rust, which you can do by visiting the official Rust site.

You can create your Rust project by entering the following command in your terminal.

> cargo new game_of_life

Get into the directory using the command

> cd game_of_life

Inside this main directory, you will see a main.rs file inside the src folder. This main.rs is the main file that will be built and executed.

Rusting Game of Life

In this example, we going to recreate a blinker, an interesting pattern in Game of Life.

In our example, the universe of Game of Life is a two-dimensional vector in Rust, which we will call it a grid, with 1 representing a live cell and 0 representing a dead cell.

The code is given below.

Then run the command given below to build and execute the file.

> cargo run

The output of the code is given below. As you can see from the output, the oscillating structure blinker in the middle of the grid.

Initial grid:
[0, 0, 0, 0, 0]
[0, 0, 1, 0, 0]
[0, 0, 1, 0, 0]
[0, 0, 1, 0, 0]
[0, 0, 0, 0, 0]
Generation 1:
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
[0, 1, 1, 1, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
Generation 2:
[0, 0, 0, 0, 0]
[0, 0, 1, 0, 0]
[0, 0, 1, 0, 0]
[0, 0, 1, 0, 0]
[0, 0, 0, 0, 0]
Generation 3:
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
[0, 1, 1, 1, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
Generation 4:
[0, 0, 0, 0, 0]
[0, 0, 1, 0, 0]
[0, 0, 1, 0, 0]
[0, 0, 1, 0, 0]
[0, 0, 0, 0, 0]
Generation 5:
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
[0, 1, 1, 1, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]

Observations

At a first glance, the Rust code looks similar to any C styled language, but there are some interesting, distinct points to note.

  • At line 33 shown below
live_neighbours += grid[new_x as usize][new_y as usize]

We need to cast the type of the variables new_x and new_y to usize. Suppose if the variable used to index an element of a vector is an integer, then we might get into overflow conditions like using negative numbers. The Rust compiler gives us an error if we index a vector with any other types except unsigned types.

  • Suppose we want to access the variables of unsigned type with signed type in calculations, then we need to cast the variables to one common type. Like at lines 28 and 29 shown below
let new_x = (i as i8) + x;let new_y = (j as i8) + y;

We need to cast the type of the variables i and j to i8 because the variables new_x, new_y, x, and y are of type i8 whereas i and j are of type usize because the variables n and m are of type usize. Also, if our code leads to some negative values of the variable of type usize, the compiler will panic, stopping the execution.

  • If we want our variables to be mutable, we must make sure to tell that to the compiler by using the keyword mut. As shown in lines 11 and 65.
let mut future: Vec<Vec<i8>> = vec![vec![0; n]; m];let mut grid: Vec<Vec<i8>> = vec![vec![0; cols]; rows];

Conclusion

I had a fun time implementing Game of Life in Rust. The development time of the program took a bit of time because the Rust compiler kept on complaining, but the execution was smooth. Whereas for other languages, we write the program, and we might run into errors at runtime, then come again to debug the code.

The Rust compiler also gives us suggestions for the errors and warnings which beat compilers of other programming languages. Often I was in doubt while checking the output because the program(in Rust) gave the output that is desired in the very first trial. This shows the power of the Rust compiler; once the code compiles then there is a high chance that the program might work.

The observations we saw coincides with the safety that Rust promises. This is just the tip of the iceberg, Rust also provides a lot of other features like borrowers, concurrency, and so on. The future of Rust while looking at the current state is bright, but will it stand the test of time; only time will tell the tale.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store