What Rust is and where it runs

Rust is a compiled, statically typed language with one unusual rule: every value has exactly one owner, and the compiler refuses a program that uses a value after it has been given away. That rule, and the borrow checker that enforces it, is what this course teaches first, because it shapes every other decision in the language.

This lesson is the tour before the work: a first program, the target the compiler builds for, the reason the language calls itself memory safe without a garbage collector, the two tools that drive a build, and the editions that decide which rules a crate follows. Everything you see was compiled and run on the machine named at the end of the page.

Hello, Rust

A Rust program starts at fn main(), the function the operating system calls. The body here is one statement: println! is a macro, not a function, which is why its name ends with an exclamation mark. The string is a literal, and the semicolon ends the statement.

rustc compiles the file to a native executable. There is no interpreter and no virtual machine in the picture: the output is an ELF binary for this machine, and it keeps running after the source file is gone.

fn main() { println!("Hello, Rust"); }

rustc --edition 2024 hello.rs
./hello
file hello

Hello, Rust
hello: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-x86_64.so.1, BuildID[sha1]=35d56d2bef2f929cad7ac5f532442b6acb6fe7c7, with debug_info, not stripped

The first line is the program. The second is the binary itself, as file describes it: a 64-bit position-independent executable for the x86-64 architecture. Nothing about it mentions Rust, because there is nothing to mention: a Rust program is machine code plus the parts of the standard library it actually used.

Where Rust runs

The compiler knows a long list of targets, from ordinary desktop and server systems to embedded boards without an operating system. The constants in std::env::consts are fixed when the crate is compiled, so the program below prints the target it was built for, not the machine it happens to run on.

This build knows 334 targets. That number is a property of the compiler version, not of the language, and it is the first place to look when a platform is claimed to be supported.

fn main() { println!("os: {}", std::env::consts::OS); println!("arch: {}", std::env::consts::ARCH); println!("family: {}", std::env::consts::FAMILY); println!("pointer: {} bits", std::mem::size_of::<usize>() * 8); }

rustc --edition 2024 target.rs
./target
rustc --print target-list | wc -l

334
os: linux
arch: x86_64
family: unix
pointer: 64 bits

A binary compiled for one target does not run on another: an x86_64-linux executable is not an ARM board's program. Cross-compilation is a matter of naming another target to the compiler, and the lesson on installing the toolchain lists the pages for each system.

Memory safety without a collector

Languages without a garbage collector usually make the programmer responsible for memory, and languages that take the responsibility away usually collect garbage at run time. Rust takes a third route: the compiler proves that every value has one owner, and the value is released when that owner goes out of scope.

Assignment is a move, not a copy, for a type that owns heap memory. After let copy = greeting; the value belongs to copy, and greeting may no longer be used. The program below breaks that rule, and the compiler refuses to build it: this is not a run-time check that costs anything, and the same mistake in C++ would compile and then read freed memory.

fn main() { let greeting = String::from("hello"); let copy = greeting; println!("{}", greeting); println!("{}", copy); }

rustc --edition 2024 moved.rs

error[E0382]: borrow of moved value: `greeting`
--> moved.rs:4:20
|
2 | let greeting = String::from("hello");
| -------- move occurs because `greeting` has type `String`, which does not implement the `Copy` trait
3 | let copy = greeting;
| -------- value moved here
4 | println!("{}", greeting);
| ^^^^^^^^ value borrowed here after move
|
help: consider cloning the value if the performance cost is acceptable
|
3 | let copy = greeting.clone();
| ++++++++

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0382`.

Read the message from the top: the error has a code, the value was moved on line 3, and the use on line 4 is the borrow of a value that is no longer there. The compiler also offers a fix, clone(), and that fix is honest about its cost: a clone allocates and copies. Ownership and moves have their own lesson, Ownership and moves, later in the course.

rustc and cargo

Two programs drive everything. rustc compiles a single crate, and it is all a one-file program needs. cargo runs rustc for you, downloads dependencies, keeps a lock file and knows where the output goes.

The flags matter from the first day. Without -O the build keeps debug assertions and overflow checks, which is what you want while writing code; with -O it is a release build, and an arithmetic overflow stops being a panic. --edition selects the language rules, which the next section explains.

fn main() { println!("debug assertions: {}", cfg!(debug_assertions)); }

rustc --version --verbose
cargo --version
rustc --edition 2024 flags.rs -o flags_debug
./flags_debug
rustc -O --edition 2024 flags.rs -o flags_release
./flags_release

rustc 1.96.1 (31fca3adb 2026-06-26) (Alpine Linux Rust 1.96.1-r0)
binary: rustc
commit-hash: 31fca3adb283cc9dfd56b49cdee9a96eb9c96ffd
commit-date: 2026-06-26
host: x86_64-alpine-linux-musl
release: 1.96.1
LLVM version: 22.1.3
cargo 1.96.1 (356927216 2026-06-26) (Alpine Linux Rust 1.96.1-r0)
debug assertions: true
debug assertions: false

Both binaries came from one source file. The debug build reports that assertions are on; the release build reports they are off, because -O turns them off along with the overflow checks. A program that depends on this without saying so is a bug waiting for a release build.

Editions

Rust does not break old code in a new compiler version. Instead, a crate declares an edition in its manifest, and the compiler applies that edition's rules. Edition 2015, 2018, 2021 and 2024 differ in small, explicit ways: a keyword is reserved, a closure captures fields instead of the whole struct, a trait object must be written with dyn.

The program below calls a function named gen. Edition 2024 reserved that word for generator syntax, so the same file is a syntax error there and a working program in edition 2021. This is exactly the sort of change an edition is allowed to make, and it is why every command in this course names the edition.

fn gen() -> u32 { 7 } fn main() { println!("gen: {}", gen()); }

rustc --edition 2024 editions.rs
rustc --edition 2021 editions.rs
./editions

error: expected identifier, found reserved keyword `gen`
--> editions.rs:1:4
|
1 | fn gen() -> u32 {
| ^^^ expected identifier, found reserved keyword
|
help: escape `gen` to use it as an identifier
|
1 | fn r#gen() -> u32 {
| ++

error: expected expression, found reserved keyword `gen`
--> editions.rs:6:25
|
6 | println!("gen: {}", gen());
| ^^^ expected expression

error: aborting due to 2 previous errors

gen: 7

The compiler does not just reject the name, it shows the escape hatch: r#gen is the raw identifier that works in any edition. The lesson on editions comes back to this when the course reads old crates that predate the 2024 rules.

Exercises

  1. Change hello.rs so it prints two lines, then compile it again. Which command would you use to put the binary in a different directory?
  2. Apply the fix that the E0382 message suggests in moved.rs. Run the program, then explain what the fix allocated that the original code did not.
  3. Compile editions.rs with --edition 2018 and with --edition 2021. Do both accept the function name, and which edition introduced the keyword?

Questions

A new set of questions in a new order every time. Choose one answer for each, then check.

  1. What is the role of fn main in a Rust program?

    Answer

    It is the entry point that the operating system calls

    Every executable crate starts at main, and rustc refuses a binary crate without it.

  2. What is println!?

    Answer

    A macro that writes a formatted line to standard output

    The exclamation mark marks a macro, and the macro formats its arguments and writes them out.

  3. What does rustc --edition 2024 hello.rs produce?

    Answer

    A native executable for the host target

    rustc compiles to machine code, and the output is an ordinary executable for one target.

  4. What is an edition in Rust?

    Answer

    A set of language rules that a crate opts into

    An edition may reserve a keyword or change a rule, and crates choose their edition in Cargo.toml.

  5. What does this program print? fn main() { println!("Hello, {}", "Rust"); }

    Answer

    Hello, Rust

    The braces are filled from the arguments in order, so the string follows the comma and space.

  6. What does rustc --print target-list | wc -l count?

    Answer

    The targets this compiler can build for

    The target list is the set of platforms the compiler knows how to produce code for.

  7. What does the compiler say about this program? fn main() { let greeting = String::from("hello"); let copy = greeting; println!("{greeting}"); }

    Answer

    error[E0382]: borrow of moved value: greeting

    A String owns its buffer, so the assignment moves it and the later use is a borrow after a move.

  8. What does this program print? fn main() { let greeting = String::from("hello"); println!("{greeting}"); }

    Answer

    hello

    The binding owns the string, and the placeholder prints its contents without another borrow.

  9. What is the first line of the compiler output for this program with --edition 2024? fn gen() -> u32 { 7 } fn main() { println!("gen: {}", gen()); }

    Answer

    error: expected identifier, found reserved keyword gen

    Edition 2024 reserves the word, and the parser reports the reserved keyword at the name.

  10. What does cfg!(debug_assertions) report in a build made without -O?

    Answer

    true, because the debug profile keeps assertions

    Without optimisation rustc keeps debug assertions and overflow checks, and cfg! reads that flag.

The code on this page was built with rustc 1.96.1 and cargo 1.96.1 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container.

Article author: Arthur Isaev