Getting Started with Rust | Memory-Safe Systems Programming
이 글의 핵심
A practical guide to getting started with Rust: a memory-safe systems language, Cargo, ownership and borrowing basics, types, and examples you can run today.
Introduction
What is Rust?
Rust is a memory-safe systems programming language developed by Mozilla (now stewarded by the Rust Project).
Highlights:
- ✅ Memory safety: enforced at compile time
- ✅ Zero-cost abstractions: no extra runtime overhead for idiomatic code
- ✅ Fearless concurrency: data-race freedom checked by the compiler
- ✅ Performance: on par with C/C++
- ✅ Tooling: Cargo for builds and dependencies
Rust vs C++:
| Aspect | Rust | C++ |
|---|---|---|
| Memory safety | Compile time (by default) | Runtime (optional tooling) |
| Null pointers | Avoided with Option | Possible |
| Package management | Cargo | CMake, vcpkg, Conan, etc. |
| Learning curve | Steep | Very steep |
1. Installation
Installing rustup
Windows:
- Download from rustup.rs
- Run the installer
Mac/Linux:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Verify the install
rustc --version
cargo --version
2. Hello World
Create a project
cargo new hello_rust
cd hello_rust
src/main.rs
fn main() {
println!("Hello, Rust!");
}
Run
cargo run
3. Cargo
Project layout
hello_rust/
├── Cargo.toml
├── Cargo.lock
└── src/
└── main.rs
Cargo.toml
[package]
name = "hello_rust"
version = "0.1.0"
edition = "2021"
[dependencies]
Common commands
cargo new project_name # New project
cargo build # Build
cargo run # Build and run
cargo test # Run tests
cargo check # Fast typecheck
cargo build --release # Release build
4. Basic syntax
Variables
fn main() {
// Immutable by default
let x = 5;
// x = 6; // Error!
// Mutable
let mut y = 5;
y = 6; // OK
// Explicit type
let z: i32 = 10;
}
Functions
fn add(a: i32, b: i32) -> i32 {
a + b // `return` can be omitted
}
fn main() {
let result = add(10, 20);
println!("result: {}", result);
}
5. Ownership
Core idea
Rust’s ownership model is central to memory safety:
fn main() {
// String::from allocates on the heap
let s1 = String::from("hello");
// Ownership moves from s1 to s2
// s1 is invalidated and must not be used
let s2 = s1;
// println!("{}", s1); // Compile error!
// "value borrowed here after move"
// s1 no longer owns the data
println!("{}", s2); // OK — s2 owns the string
}
Why?
- In C++, after
s2 = s1both names may still be valid → risk of double free - Rust moves ownership so only one owner exists → safe deallocation
References (borrowing)
Use a reference to borrow without transferring ownership:
fn main() {
let s1 = String::from("hello");
// &s1: immutable borrow; s1 still owns the data
let len = calculate_length(&s1);
// s1 is still valid
println!("{} length: {}", s1, len);
}
fn calculate_length(s: &String) -> usize {
// s is a reference only; no ownership
// Dropping s does not free the heap data
s.len()
}
Borrowing rules:
- Any number of immutable references (
&T) at once (read-only) - At most one mutable reference (
&mut T) at a time (exclusive write) - Immutable and mutable borrows cannot overlap
Mutable references
To mutate through a borrow, use &mut:
fn main() {
let mut s = String::from("hello");
change(&mut s);
println!("{}", s); // hello, world
}
fn change(s: &mut String) {
s.push_str(", world");
}
Mutable borrow restrictions:
let mut s = String::from("hello");
let r1 = &mut s;
// let r2 = &mut s; // Error!
// "cannot borrow `s` as mutable more than once at a time"
// Only one active mutable borrow → no data races
r1.push_str(" world");
What the compiler gives you:
- No overlapping mutable borrows → no data races
- While references exist, the owner cannot invalidate them → no dangling pointers
- All checked at compile time → no runtime GC for these guarantees
6. Data types
Scalar types
// Integers
let a: i8 = 127;
let b: i32 = 2147483647;
let c: u32 = 4294967295;
// Floats
let x: f32 = 3.14;
let y: f64 = 3.14159;
// Boolean
let t: bool = true;
let f: bool = false;
// char (Unicode scalar)
let c: char = 'A';
let emoji: char = '😀';
Compound types
// Tuple
let tup: (i32, f64, char) = (500, 6.4, 'A');
let (x, y, z) = tup;
println!("{}, {}, {}", x, y, z);
// Array (fixed size, stack)
let arr = [1, 2, 3, 4, 5];
let first = arr[0];
7. Hands-on example
Mini calculator
fn main() {
println!("=== Calculator ===");
let a = 10;
let b = 5;
println!("{} + {} = {}", a, b, add(a, b));
println!("{} - {} = {}", a, b, subtract(a, b));
println!("{} * {} = {}", a, b, multiply(a, b));
println!("{} / {} = {}", a, b, divide(a, b));
}
fn add(a: i32, b: i32) -> i32 { a + b }
fn subtract(a: i32, b: i32) -> i32 { a - b }
fn multiply(a: i32, b: i32) -> i32 { a * b }
fn divide(a: i32, b: i32) -> i32 { a / b }
Summary
Takeaways
- Rust: memory-safe systems language with strong tooling
- Cargo: build tool and package manager
- Ownership: foundation of memory and thread safety
- Immutability: default for bindings (
letvslet mut) - Performance: comparable to C/C++ when optimized
Next steps
- Rust ownership
- Structs and enums
- Error handling
Related posts
- Top 15 beginner mistakes in C++ | From compile errors to runtime crashes
- C++ and Rust: interoperability and the memory-safety debate [#44-2]
- C++ vs Rust: ownership, safety, errors, concurrency, and performance
- Rust memory safety deep dive | Ownership, borrow checker, lifetimes, unsafe
- Rust vs C++ memory safety | Compiler errors compared [#47-3]