Node to Rust — Day 4: Hello World (and your first two WTFs)
December 4, 2021
Introduction
If you’ve never worked with a language that compiles down to native binaries, you’re going to have fun with Rust. Yeah, it’s easy to distribute executables within your chosen community, no matter if its node.js, Python, Ruby, PHP, Java, or something else. It gets crusty fast when you have to explain to outsiders. Think about the last time you enjoyed installing the latest version of Java to run a jar or had to deal with Python’s virtual environments to run some random tool you wish was in JavaScript.
With Rust, you can distribute executable binaries like the big kids. Sure you may need to cross-compile it for other architectures and operating systems, but Rust has got you there, too.
Quick links
- Day 1: From nvm to rustup
- Day 2: From npm to cargo
- Day 3: Setting up VS Code
- → Day 4: Hello World (and your first two WTFs)
- Day 5: Borrowing & Ownership
- Day 6: Strings, part 1
- Day 7: Syntax and Language, part 1
- Day 8: Language Part 2: From objects and classes to HashMaps and structs
- Day 9: Language Part 3: Class Methods for Rust Structs (+ enums!)
- Day 10: From Mixins to Traits
- Day 11: The Module System
- Day 12: Strings, Part 2
- Day 13: Results & Options
- Day 14: Managing Errors
- Day 15: Closures
- Day 16: Lifetimes, references, and
'static
- Day 17: Arrays, Loops, and Iterators
- Day 18: Async
- Day 19: Starting a large project
- Day 20: CLI Arguments & Logging
- Day 21: Building and Running WebAssembly
- Day 22: Using JSON
- Day 23: Cheating The Borrow Checker
- Day 24: Crates & Tools
Code
The code generated by the commands in this post can be found at wasmflow/node-to-rust
Hello world
Start your first executable project by using cargo new
with the name of your project like this
cargo new my-app
By default, cargo new
uses a template for binary applications. That’s what we want today, but keep in mind you can also do cargo new --lib
to bootstrap a library.
After you execute the command, you’ll have the following directory structure:
my-app/
├── .git
├── .gitignore
├── Cargo.toml
└── src
└── main.rs
If you had a version of Rust installed before recently, the
edition
key inCargo.toml
is probably set to2018
.2021
has since gone stable and is what this series assumes. Editions are kind of like ECMAScript versions. The differences between2018
and2021
aren’t huge, but it’s worth calling out.
Even before you take a look at the source, run your new project with cargo run
.
» cargo run
Compiling my-app v0.1.0 (./my-app)
Finished dev [unoptimized + debuginfo] target(s) in 0.89s
Running `target/debug/my-app`
Hello, world!
cargo run
builds your app with cargo build
and executes the specified (or default, in this case) binary. After running this, your binary’s already made and you can find it at ./target/debug/my-app
. Go, run it directly. It feels good.
If you want to build your app without running it, use cargo build
. Cargo builds with the dev
(debug) profile by default which is usually faster. It will retain debug information at the expense of file size and performance. When you are ready to release, you’d build with cargo build --release
and you’d find your binary in ./target/release/my-app
.
Now to the Rust
Take a look at src/main.rs
and mentally process this wild new language:
fn main() {
println!("Hello, world!");
}
Well that’s not so bad, right?
The main()
function is required in standalone executables. It’s the entrypoint to your CLI app.
println!()
is a macro that generates code to print your arguments to STDOUT. If you’ve never dealt with macros before, they’re like inline transpilers that generate code during compilation. We’ll get to macros later.
"Hello, world!"
is a string slice which is where things start getting real rusty. Strings are the first major hurdle for new Rust users and we’ll tackle those in an upcoming post, but let’s walk through some of the first WTFs here to set the stage.
Strings WTF #1
First, assign “Hello, world!” to a variable using let
and try to print it. Yep, Rust uses let
and const
keywords just like JavaScript, though where you want to use const
just about everywhere in JavaScript, you want to use let
in most Rust.
fn main() {
let greeting = "Hello, world!";
println!(greeting);
}
If you set up VS Code like we did in an earlier day, you’ll already see an error. Run it anyway with cargo run
.
$ cargo run
Compiling day-4-strings-wtf-1 v0.0.0 (/path/node-to-rust/crates/day-4/strings-wtf-1)
error: format argument must be a string literal
--> crates/day-4/strings-wtf-1/src/main.rs:3:12
|
3 | println!(greeting);
| ^^^^^^^^
|
help: you might be missing a string literal to format with
|
3 | println!("{}", greeting);
| +++++
error: could not compile `day-4-strings-wtf-1` due to previous error
If you expected this to work, you’d be normal. In most languages a string is a string is a string. Not in Rust. Do pay attention to the error message though. Rust’s error messages are leagues beyond the error messages you’re probably used to. This message not only describes the problem, but shows you exactly where it occurs AND tells you exactly what you need to do to fix it. println!()
requires a string literal as the first argument and supports a formatting syntax for replacing portions with variables. Change your program to the following to get back on track.
fn main() {
let greeting = "Hello, world!";
println!("{}", greeting);
}
Strings WTF #2
As a seasoned programmer, you know how write reusable code and are probably itching to abstract this complex logic into a reusable function. Take your newfound knowledge of println!()
formatting syntax and write this beauty below.
fn main() {
greet("World");
}
fn greet(target: String) {
println!("Hello, {}", target);
}
Intuitively, this looks fine. But when you run it…
$ cargo run
Compiling day-4-strings-wtf-2 v0.0.0 (/path/node-to-rust/crates/day-4/strings-wtf-2)
error[E0308]: mismatched types
--> crates/day-4/strings-wtf-2/src/main.rs:2:9
|
2 | greet("World");
| ^^^^^^^- help: try using a conversion method: `.to_string()`
| |
| expected struct `String`, found `&str`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `day-4-strings-wtf-2` due to previous error
While rustc
’s error messages do hint at how to get you back up and running, it does little to explain WTF is really going on…
Wrap-up
Wrapping your head around strings in Rust is important. I know it’s a tease to go through stuff like this without an immediate answer, but we’ll get to it ASAP. First though, we need to talk about what “ownership” means in Rust in Day 5: Borrowing & Ownership.
These questions are why I started this series. Now would be a good time to start searching the web for answers on Rust strings so you have some perspective on things when you come back. If you need a starter, check these out
- Strings in the Rust docs
- Why Are There Two Types of Strings In Rust?
- How do I convert a &str to a String in Rust?
- Rust String vs str slices
- Rust: str vs String
- String vs &str in Rust
As always, you can reach me personally on twitter at @jsoverson, the Candle team at @candle_corp, and our Discord channel.