Getting Started with Rust | Memory-Safe Systems Programming

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++:

AspectRustC++
Memory safetyCompile time (by default)Runtime (optional tooling)
Null pointersAvoided with OptionPossible
Package managementCargoCMake, vcpkg, Conan, etc.
Learning curveSteepVery steep

1. Installation

Installing rustup

Windows:

  1. Download from rustup.rs
  2. 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 = s1 both 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

  1. Rust: memory-safe systems language with strong tooling
  2. Cargo: build tool and package manager
  3. Ownership: foundation of memory and thread safety
  4. Immutability: default for bindings (let vs let mut)
  5. Performance: comparable to C/C++ when optimized

Next steps


  • 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]