crust / Part 1 - Introduction

Rusty boat: attribution https://www.pexels.com/photo/a-rusty-boat-on-the-seashore-9816335/

Welcome to another research series! Toward the end of 2020 I wanted to learn something new again and was almost going to look into the latest version of C++ but something in the back of my brain reminded me of a programming language I’d heard of but didn’t know much about: Rust.

After a bit of basic research, Rust looked like it could be an interesting language to learn because it is designed for high performance but can also be compiled for many different platforms. The Rust language also seems to be picking up steam in popularity and usage in mainstream software and the language itself on the surface looked relatively pleasant to program in (compared to, say C/C++).

I decided to go for it and see what I could do with Rust - I chose to re-implement my older research project A Simple Triangle with Rust to see if I could build a cross platform code base that I could deploy on mobile phones as well as desktop machines.

We will refer to the Rust project in this series as crust (Cross platform Rust) because, it sounds kinda catchy and easy to remember and type!


During this project I really enjoyed the challenge of learning to use the Rust language, toolchains and build systems - I actually rewrote the code base multiple times along the way as a result of numerous mini revelations that often happen when learning something new and interesting. It has taken me over a year of tinkering to feel comfortable with the content I’ll show in this series, though I know there is much more to learn.

I’ll be walking through the version of my approach which tries to keep the Rust programming as simple as possible for the sake of clarity - I did actually implement a version using a hand rolled trait driven dependency injection framework enabling components to be mocked for unit testing, however by the time I had scaffolded in the necessary architecture changes the code base was looking a lot more complex.

Possibly in a more industrial grade application of Rust you might want to be writing test suites etc, but I found that the Rust language itself is a lot harder to architect to support polymophism and interface driven injection than other languages, primarily due to the nature of Rust’s borrower and object ownership mechanics and the fact that it is not an object oriented language (there isn’t even any such thing as a constructor - if you have done any development using Go you will know what I mean!).


What we are going to build

Here is our crust application compiled to the Emscripten target platform. Use the arrow keys keys to move around and A and Z to move up and down, or click near the edges of the scene with your mouse / finger:

Our main Rust project will be able to be cross compiled into the following target platforms via a Rust command line application which we will be authoring alongside the actual 3D application:

Note: I don’t think it would be too hard to add a Linux target either but I haven’t implemented it in this series.


The approach

I started off this project by building a single Rust application which renders 3D objects using SDL2 as the low level framework for managing graphics and other media functions, then wrote all the supporting build orchestration code in Bash and PowerShell scripts.

Although using Bash and PowerShell scripts to orchestrate the build worked reasonably well (and was quite similar to what I’d done in A Simple Triangle) it dawned on me that one of the primary use cases for using the Rust language was to write command line or systems level applications, for which the use case of building our main application seemed a perfect fit. I had also found that a script driven approach required rewriting swathes of Bash scripts again in PowerShell to allow the cross platform targets to be built on both Windows and MacOS. I decided that having to duplicate these scripts wasn’t a particularly awesome outcome.

So after a brief amount of hesitation, I chucked away all my Bash and PowerShell scripts and instead introduced a Rust command line application named crust-build whose job it was to actually build our main Rust application targets - doing all the build orchestration and stitching together the bits and pieces needed to create the resulting self contained 3D application.

By taking the Rust CLI build tool driven approach I was able to drastically reduce the build code duplication and end up with a Rust driven cross platform approach to building our main project - leaving almost no Bash or PowerShell scripts in the solution. This to me felt like a big win and authoring the CLI application itself was a great exercise in Rust development.

Even if you aren’t interested in 3D engines you can hopefully still gain something from seeing how to create the Rust command line builder application.

Our (final) project structure will represent the two key Rust applications we will author (crust-build and crust-main), along with the set of target platforms we want to cross compile into - some of which need support from non Rust project files to build:

: root
    + android:        Android application
    + crust-build:    Rust command line application to build the main app
    + crust-main:     Main Rust source code and assets
    + emscripten:     Emscripten browser application
    + ios:            iOS application
    + macos-console:  MacOS console application - used for development work
    + macos-desktop:  MacOS bundled desktop application  
    + windows:        Windows desktop application

For this series I have used only OpenGL as the 3D rendering stack as it is relatively easy to get up and running - maybe some time in the future I might introduce Vulkan support also, though Vulkan is a beast to get up and running comparatively so it depends how adventurous and motivated I feel later on :)


Environment setup

During this project I tried a few different tools for authoring Rust code with the following constraints in mind:

I really wanted to use IntelliJ as it is an awesome IDE and is cross platform, however the IntelliJ Community Edition along with the community edition Rust plugin (at the time of writing this anyway) DOES NOT SUPPORT DEBUGGING. To debug Rust in IntelliJ you need the Professional edition which … is not free. Sorry Jetbrains I’m out - hopefully the community edition plugin will mature and provide this in the future.

Instead, I landed on using Microsoft Visual Studio Code, which is very lightweight and with a couple of extensions installed can debug Rust code. I wouldn’t say it is awesome when it comes to code introspection but it is servicable and was sufficient to get this project done. I suspect that as Rust grows in popularity, the free options available will improve over time.

I actually did the bulk of the Rust programming on my Windows machine, but was able to swap back and forth with my Mac machine without much hassle at all which was one of the key outcomes I wanted. You can follow this series on either operating system, however some of the Apple related target platforms naturally require a Mac (and of course the Windows target platform needs a Windows machine).

So, before we begin, set up your working environment like so:

Common

Windows

MacOS

Once you have your environment setup, check you have Rust successfully installed via a terminal session (your version of Rust will likely be more recent than 1.59.0):

$ rustc --version
rustc 1.59.0

Note: This project was written to work against Rust 1.59.0 - it should work on more recent versions, however like with any software under active development, it is possible that a future Rust version may introduce breaking changes. In fact during this project, Rust 2021 Edition was released which broke a bunch of stuff in my code base including some third party library dependencies which I had to do a major refactor to fix.


When you are ready to rock n roll, proceed to setup the foundation for crust!

Continue to Part 2: Foundation setup.

End of part 1