Node to Rust — Day 4: Hello World (and your first two WTFs)

Node to Rust — Day 4: Hello World (and your first two WTFs)

Authors
Jarrod Overson

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.

Code

The code generated by the commands in this post can be found at vinodotdev/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 in Cargo.toml is probably set to 2018. 2021 has since gone stable and is what this series assumes. Editions are kind of like ECMAScript versions. The differences between 2018 and 2021 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:

1
2
3
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.

1
2
3
4
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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$ 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.

1
2
3
4
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.

1
2
3
4
5
6
7
fn main() {
  greet("World");
}

fn greet(target: String) {
  println!("Hello, {}", target);
}

Intuitively, this looks fine. But when you run it…

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ 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

As always, you can reach me personally on twitter at @jsoverson, the Vino team at @vinodotdev, and our Discord channel.