In the ongoing Letter Boxed solver saga, we have explored native code from the C++ angle. After all, C++ is still comfortably among the top of the close-to-the-metal programming languages (at least according to some sources). But it’s 2021 AD — why not explore a modern native language?
This is where Rust comes in. Rust purports to be a vehicle for reliable and efficient systems without the “old pitfalls” of traditional (read: C/C++) development platforms. These are bold claims, indeed. Let’s see if Rust can prove itself via the Letter Boxed challenge!
Before we get into the actual implementation tasks, we need to prepare our development environment. As discussed before, we want to support both command line and IDE build workflows as simply as possible. Let’s start with the IDE which, according to many people in the Rust community, ought to be Visual Studio Code. The must-have plugins for Rust development include Aleksey Kladov‘s rust-analyzer and Ferenc Tamás‘s even-better-toml (for the ubiquitous TOML configuration files used by the Rust ecosystem). To ensure everyone is on the same page with these plugins, we can mark them as recommended extensions for our workspace: [see commit on GitHub]
Now, we need to get familiar with Cargo, the Rust package manager and all-around Swiss army knife of the Rust toolchain. As an analogy, think Rust : Cargo :: C# : .NET CLI (i.e. dotnet.exe).
For the Letter Boxed project, we will need to create a package with a single binary (executable) target. You might wonder how we can get away with just a single EXE here when we had three separate projects for our C++ example (a static library with the core logic, a unit test DLL, and the solver application). Surely we’re going to write tests, right?! Ah, but that is the beauty of Rust — it treats testing as first-class concern and has built-in testing facilities. Unit tests are written inline within each Rust module, can be automatically executed by Cargo, and will be excluded from the built target (via the
#[cfg(test)] annotation). No worries!
Despite the fact that we have only one package, we’ll anyway start with a single project workspace specified via a Cargo.toml at the repo root. Then we need to create the binary package which we’ll call
letter_boxed_solver (using the naming convention codified in RFC 430). We’ll start with the most barebones implementation for now involving a single main method which calls a library function within the same package, and a corresponding unit test: [see commit on GitHub]
So far, so good. Within VS Code everything compiles and runs. However, we have a slight issue. The default output folder used by Cargo is
target within the workspace root. This differs from the single output folder
out\build used by every other project. This, however, can be customized. We can specify a Cargo config file which overrides target-dir to our common output folder instead: [see commit on GitHub]
That wraps up the IDE support. Now we can move on to the command line experience. Of course, it’s possible to directly use
cargo ... commands but we already wrapped all the other build and test steps into our
build.cmd script. Let’s do the same for Rust: [see commit on GitHub]
We could stop here, but you might have noticed one final discrepancy. The Rust build targets by default go to
release folders within the output path. However, up to this point, we were using
x64-Release for the C# and C++ projects. Since we are unlikely to require any other architecture than what is present on the host machine, it is safe to assume we could drop the architecture prefix for those other projects: [see commit on GitHub]
That is enough for one day. But now we can hit the ground running with real Rust coding — next time!
Pingback: Letter Boxed: Rust impl, part 1 (basics) – WriteAsync .NET