Hello World in Rust
12 min read
Your first Rust program! We’ll create a classic “Hello World” and compare it to TypeScript/JavaScript.
Quick Overview
Section titled “Quick Overview”Rust programs start with a main function, just like many other languages. Unlike JavaScript, which is interpreted, Rust is compiled to a native executable.
Time required: 15-20 minutes
TypeScript/JavaScript Example
Section titled “TypeScript/JavaScript Example”Let’s start with something familiar:
function main() { console.log("Hello, world!");}
main();Running it:
# Option 1: With ts-nodets-node hello.ts
# Option 2: Compile then runtsc hello.ts # Creates hello.jsnode hello.js # Runs the JS fileWhat happens:
- TypeScript is transpiled to JavaScript
- Node.js interprets the JavaScript
- V8 JIT-compiles hot code paths
- Output appears in terminal
Rust Equivalent
Section titled “Rust Equivalent”fn main() { println!("Hello, world!");}Running it:
# Compilerustc hello.rs # Creates executable 'hello'
# Run./hello # macOS/Linuxhello.exe # WindowsWhat happens:
- Rust is compiled to native machine code
- Linker creates standalone executable
- No runtime or interpreter needed
- Output appears in terminal
Detailed Explanation
Section titled “Detailed Explanation”Line-by-Line Comparison
Section titled “Line-by-Line Comparison”TypeScript:
function main() { // Function declaration console.log("Hello, world!"); // Print to console}
main(); // Call the functionRust:
fn main() { // Function declaration println!("Hello, world!"); // Print to console (with newline)} // main() is auto-called (entry point)Key Syntax Differences
Section titled “Key Syntax Differences”| Aspect | TypeScript | Rust |
|---|---|---|
| Function keyword | function or => | fn |
| Print function | console.log() | println!() |
| Statement end | ; optional (ASI; most styles keep it) | ; required |
| Block style | { same line or next | { same line (idiomatic) |
| Indentation | 2 spaces (common) | 4 spaces (standard) |
| Entry point | Call main() explicitly | main() called automatically |
| Macro syntax | None (no macros) | ! indicates macro |
The println! Macro
Section titled “The println! Macro”Notice the ! in println!():
println!("Hello, world!"); // Macro (notice the !)What’s a macro?
- Code that writes code at compile time
- Like a template that expands before compilation
println!is a macro, not a function
Why !?
- The
!simply marks a macro invocation — it has nothing to do with format strings - Macros can do things functions can’t (e.g. accept a variable number of arguments and check the format string at compile time)
- You’ll see it on
vec!,format!,assert!, and many others
Compare to TypeScript:
// TypeScript has no macro system// Closest equivalent: template literalsconsole.log(`Hello, world!`);Rust also has print! (without newline):
print!("Hello, "); // No newlineprint!("world!\n"); // Explicit newlineMultiple Ways to Create and Run
Section titled “Multiple Ways to Create and Run”Method 1: Single File with rustc
Section titled “Method 1: Single File with rustc”Create the file:
# Create hello.rsecho 'fn main() { println!("Hello, world!");}' > hello.rsCompile and run:
# Compilerustc hello.rs
# Run./helloPros:
- Simple and direct
- No additional files needed
- Good for learning
Cons:
- No dependency management
- No standard project structure
- Manual compilation
When to use: Quick tests, learning exercises
Method 2: Cargo Project (Recommended)
Section titled “Method 2: Cargo Project (Recommended)”Create project:
# Create new projectcargo new hello_worldcd hello_worldThis creates:
hello_world/├── Cargo.toml # Project manifest (like package.json)└── src/ └── main.rs # Your code (already has Hello World!)Run it:
cargo runOutput:
Compiling hello_world v0.1.0 (/path/to/hello_world) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.16s Running `target/debug/hello_world`Hello, world!Pros:
- Standard project structure
- Dependency management built-in
- Automatic compilation
- Testing framework included
Cons:
- Slightly more complex for tiny scripts
When to use: Any real project (always use this!)
Compare to Node.js/TypeScript:
# Node.js equivalentnpm init -y # Create package.jsonnpm install -D typescript @types/nodenpx tsc --init # Create tsconfig.json# Create src/index.tsnpm run dev # (after configuring package.json)Cargo is simpler - one command!
Method 3: Cargo Script (nightly-only, unstable)
Section titled “Method 3: Cargo Script (nightly-only, unstable)”Warning: Single-file “cargo scripts” are an unstable feature. As of Rust 1.96.0 they only work on the nightly toolchain behind the
-Zscriptflag. On stable,cargorejects an embedded manifest witherror: embedded manifest ... requires -Zscript.
When enabled, a script carries its dependencies in a --- TOML frontmatter
block at the top of the file (this is the real syntax — there is no
// cargo.toml comment manifest):
#!/usr/bin/env -S cargo +nightly -Zscript---[package]edition = "2024"---
fn main() { println!("Hello, world!");}Run directly:
chmod +x hello.rs./hello.rsWhen to use: Quick automation scripts — but only once you are on nightly and accept that the format may still change before it stabilizes. For anything real, prefer Method 2 (a Cargo project).
Key Differences from TypeScript/JavaScript
Section titled “Key Differences from TypeScript/JavaScript”1. Compilation
Section titled “1. Compilation”TypeScript:
tsc hello.ts # Transpiles to hello.jsnode hello.js # Node.js interprets
# Size: hello.js is ~same size as hello.tsRust:
rustc hello.rs # Compiles to machine code
# Size: executable is larger (~400KB for "Hello World")# But runs much faster and needs no runtimeWhy Rust binary is larger:
- Includes all dependencies statically
- No runtime needed
- Can be stripped for production
Strip symbols for smaller binary:
strip hello # Removes debug symbols (roughly a 20-30% cut for a tiny binary)2. Startup Time
Section titled “2. Startup Time”TypeScript/Node.js:
time node hello.js# real 0m0.050s (50ms)Rust:
time ./hello# real 0m0.001s (1ms)Why: Rust is native code, no runtime or interpreter startup.
3. Entry Point
Section titled “3. Entry Point”TypeScript:
// Must explicitly call mainfunction main() { console.log("Hello!");}
main(); // ← Required!Rust:
// main() is automatically called at program startfn main() { println!("Hello!");}// No explicit call needed4. Print Statement
Section titled “4. Print Statement”TypeScript/JavaScript:
console.log("Hello"); // No newline is addedconsole.log("World"); // Separate line
// Output:// Hello// WorldRust:
println!("Hello"); // Newline added automaticallyprintln!("World"); // Separate line
print!("Hello"); // No newlineprint!(" World\n"); // Manual newline
// Both output:// Hello// WorldCommon Pitfalls
Section titled “Common Pitfalls”Pitfall 1: Forgetting the ! in println
Section titled “Pitfall 1: Forgetting the ! in println”Wrong:
fn main() { println("Hello, world!"); // Error: expected function, found macro `println`}Right:
fn main() { println!("Hello, world!"); // Correct: println! is a macro}Error message:
error[E0423]: expected function, found macro `println` --> hello.rs:2:5 |2 | println("Hello, world!"); | ^^^^^^^ not a function |help: use `!` to invoke the macro |2 | println!("Hello, world!"); | +Why: println! is a macro, not a function. The ! is required.
Pitfall 2: Missing Semicolon
Section titled “Pitfall 2: Missing Semicolon”A missing ; is only an error when another statement follows it (or when a
non-() value would be silently discarded). A single trailing expression with
no semicolon is fine — it just becomes the block’s return value.
Compiles and runs (the trailing expression evaluates to ()):
fn main() { println!("Hello, world!") // OK as the last expression — no `;` needed}Error — the moment a second statement follows the un-terminated one:
fn main() { println!("Hello, world!") // now this needs a `;` println!("Again!");}Error message:
error: expected `;`, found `println` --> hello.rs:2:30 |2 | println!("Hello, world!") | ^ help: add `;` here3 | println!("Again!"); | ------- unexpected token
error: aborting due to 1 previous errorWhy: Unlike JavaScript (which inserts semicolons automatically), Rust uses
; to separate statements. The safe habit is to end every statement with ;;
omit it only on a deliberate trailing expression.
Pitfall 3: Wrong File Extension
Section titled “Pitfall 3: Wrong File Extension”Wrong:
# Creating hello.js or hello.txtecho 'fn main() { ... }' > hello.jsrustc hello.js // Won't work wellRight:
# Use .rs extensionecho 'fn main() { ... }' > hello.rsrustc hello.rs //Why: While rustc might compile it, tooling (rust-analyzer, cargo) expects .rs files.
Pitfall 4: Trying to Run the .rs File
Section titled “Pitfall 4: Trying to Run the .rs File”Wrong:
./hello.rs // Cannot execute text fileRight:
# Compile firstrustc hello.rs
# Then run the executable./hello //Why: Rust is compiled, not interpreted. You run the compiled binary, not the source file.
Best Practices
Section titled “Best Practices”1. Always Use Cargo for Projects
Section titled “1. Always Use Cargo for Projects”Don’t:
rustc my_app.rsrustc module1.rsrustc module2.rs# Manual compilation is painfulDo:
cargo new my_appcd my_appcargo run# Cargo handles everything2. Use cargo run for Development
Section titled “2. Use cargo run for Development”During development:
cargo run # Debug build, fast compileFor production:
cargo build --release # Optimized build, slow compile, fast execution./target/release/hello_worldCompare compile times:
time cargo build # ~0.5s (debug)time cargo build --release # ~3s (release, but executable is 10x faster)3. Format Your Code
Section titled “3. Format Your Code”# Format a single filerustfmt hello.rs
# Format entire projectcargo fmt
# Check without formattingcargo fmt -- --checkRust standard style:
- 4-space indentation (not 2!)
- Opening brace on same line
- No trailing commas in single-line
4. Use println! for Debugging
Section titled “4. Use println! for Debugging”fn main() { let x = 42; println!("The value is: {}", x); // Format with variables println!("x = {x}"); // Shorter syntax (Rust 2021+) println!("Multiple: {} and {}", x, 10); // Multiple values}Compare to TypeScript:
const x = 42;console.log("The value is:", x); // Comma-separatedconsole.log(`The value is: ${x}`); // Template literalReal-World Example: Slightly More Complex Hello World
Section titled “Real-World Example: Slightly More Complex Hello World”Let’s make it more interesting:
TypeScript:
function greet(name: string): void { console.log(`Hello, ${name}!`);}
function main(): void { const names = ["Alice", "Bob", "Charlie"]; names.forEach((name) => greet(name));}
main();Rust:
fn greet(name: &str) { println!("Hello, {}!", name);}
fn main() { let names = vec!["Alice", "Bob", "Charlie"]; for name in names { greet(name); }}Run it:
# Save as greet.rsrustc greet.rs./greetOutput:
Hello, Alice!Hello, Bob!Hello, Charlie!Key differences:
&stris a string slice (we’ll learn this in section 02)vec![]is a vector (like an array)forloop instead of.forEach()- No
voidreturn type needed
Further Reading
Section titled “Further Reading”Official Documentation
Section titled “Official Documentation”Related Topics
Section titled “Related Topics”Exercises
Section titled “Exercises”Exercise 1: Basic Hello World
Section titled “Exercise 1: Basic Hello World”Create and run a Hello World program using both methods:
# Method 1: rustcecho 'fn main() { println!("Hello from rustc!"); }' > hello1.rsrustc hello1.rs./hello1
# Method 2: cargocargo new hello2cd hello2cargo runExercise 2: Modify the Message
Section titled “Exercise 2: Modify the Message”Change the program to print your name:
fn main() { println!("Hello, <YOUR_NAME>!");}Exercise 3: Multiple Prints
Section titled “Exercise 3: Multiple Prints”Create a program that prints multiple lines:
fn main() { println!("Line 1"); println!("Line 2"); println!("Line 3");}Exercise 4: Print Without Newline
Section titled “Exercise 4: Print Without Newline”Use print! to print on the same line:
fn main() { print!("Hello, "); print!("world!"); println!(); // Add newline at end}Exercise 5: Format with Variables
Section titled “Exercise 5: Format with Variables”fn main() { let name = "Rustacean"; let age = 5; println!("My name is {} and I am {} years old", name, age);}All Solutions
All solutions are provided in the exercises above! Try modifying them and running with cargo run.
Summary
Section titled “Summary”What you’ve learned:
- Basic Rust syntax (
fn main,println!) - How to compile with
rustc - How to create projects with
cargo - Differences between
print!andprintln! - Key differences from TypeScript/JavaScript
Key syntax:
fn main() { // Entry point println!("Text"); // Print with newline println!("Value: {}", x); // Print with formatting}Commands to remember:
rustc file.rs # Compile single filecargo new project # Create new projectcargo run # Build and runcargo fmt # Format codeYou’re ready to learn Cargo in depth!