In my last blog post I said I wanted to spend some time learning new things. The first of those is Rust. I had previously tried learning it, but got distracted before I got very far.
Since one of the things I'd use Rust for is web pages, I decided to learn how to compile to WebAssembly, how to interface with Javascript, and how to use WebSockets. At home, I use a Mac to work on my web projects, so for Rust I am compiling a native server and a wasm client. But I also wanted to try running this on redblobgames.com, which is a Linux server. How should I compile to Linux? My first thought was to use my Linux machine at home. I can install the Rust compiler there and compile the server on that machine. Alternatively, I could use a virtual machine running Linux. Both of these options seemed slightly annoying.
I've been curious how much work it would take to cross-compile, and I found this great post from Tim Ryan. My setup is simpler than his, so I didn't need everything he did. I started with these commands from his blog post:
rustup target add x86_64-unknown-linux-musl brew install FiloSottile/musl-cross/musl-cross mkdir -p .cargo cat >>.cargo/config <<EOF [target.x86_64-unknown-linux-musl] linker = "x86_64-linux-musl-gcc" EOF
I then compiled for Linux:
TARGET_CC=x86_64-linux-musl-gcc cargo build --release --target=x86_64-unknown-linux-musl
Unfortunately this failed with an error about OpenSSL. Tim's post has a solution to this. Before implementing that complicated solution I realized that I should't need SSL/TLS anyway. My server talks regular websockets, not secure websockets, and then I use nginx to proxy them into secure websockets. So I disabled the secure websockets with this in Cargo.toml
, the file that has the Rust project configuration:
[target.'cfg(target_arch = "x86_64")'.dependencies] tungstenite = { version = "0.9", default-features = false, features = [] }
At first I tried features = [] but that wasn't good enough. I needed to also use default-features = false to disable the TLS. With this, the binary built, and I was able to run it on Linux!
So now I have a Makefile
that builds the wasm client, the Mac server for local testing, and the Linux server for production. Fun!
BUILD = build RS_SRC = $(shell find src -type f -name '*.rs') Cargo.toml WASM = target/wasm32-unknown-unknown/debug/rust_chat_server.wasm run-server: target/debug/chat_server # local testing server RUST_BACKTRACE=1 cargo run --bin chat_server target/debug/chat_server: $(RS_SRC) # production server cargo build --bin chat_server target/x86_64-unknown-linux-musl/release/chat_server: $(RS_SRC) TARGET_CC=x86_64-linux-musl-gcc cargo build \ --release --target=x86_64-unknown-linux-musl $(WASM): $(RS_SRC) cargo build --lib --target wasm32-unknown-unknown $(BUILD)/rust_chat_server_bg.wasm: $(WASM) index.html wasm-bindgen --target no-modules $< --out-dir $(BUILD) mkdir -p $(BUILD) cp index.html $(BUILD)/
My Cargo.toml file is kind of terrible but it works so far for building the three outputs:
[package] name = "rust_chat_server" version = "0.1.0" authors = ["Amit Patel <redblobgames@gmail.com>"] edition = "2018" [lib.'cfg(target_arch = "wasm32")'] crate-type = ["cdylib"] [[bin]] name = "chat_server" path = "src/chat_server.rs" [dependencies] wasm-bindgen = "0.2" serde = { version = "1.0", features = ["derive"] } bincode = "1.2" [target.'cfg(target_arch = "x86_64")'.dependencies] tungstenite = { version = "0.9", default-features = false, features = [] }
That's it for now. I'm not a big fan of writing client-server code in large part because I want my pages to still work in thirty years, and that's best if there's no server component. But I want to spend time this year learning things for myself rather than trying to produce useful tutorials, so I'm going to explore this.
Tim's blog post was a huge help. Without it, I would've compiled the server on Linux. Thanks Tim!
I've placed it on github.