Rust Tutorial for Beginners: Ownership, Types and a Text Counter
Build a small Rust text analyser and trace what the compiler sees. The example connects basic types and control flow to moves, borrows and owned results.
KnowledgeGate Team
Exam prep & CS education

Rust syntax is not a beginner's main obstacle. The surprise is that every value has one owner, and the compiler checks each move and borrow. One program will count words and characters, filter by length, and expose these rules without explicit lifetime syntax. If you are trying to choose a programming path, use the wider catalogue for context, but it has no Rust-specific course.
Rust basics: bindings, concrete types, and expressions
let creates an immutable binding by default. Add mut only when the value must change:
let threshold: usize = 5;
let mut attempts: u8 = 3;
attempts -= 1;The resulting attempts is 2, while threshold remains 5. Rust often infers types, while annotations document constraints. u8 and bool are scalars. String and Vec<T> are owned collections.
Blocks and match expressions can return values:
let status = match attempts {
0 => "stop",
1 | 2 => "retry",
_ => "ready",
};Here, status == "retry". The _ fallback covers every remaining u8 value. For wider context, compare Rust's model in the Programming Languages Survey.
Model the analyser with a struct, enums, and Result
The struct Report { words: usize, chars: usize, matches: Vec<String> } groups named result fields. Modes are Mode::Summary and Mode::LongWords(usize). Expected failures are AnalyseError::EmptyInput and AnalyseError::ZeroLimit. LongWords(5) carries the threshold 5.
The exact function contract is:
fn analyse(text: &str, mode: Mode) -> Result<Report, AnalyseError>Ok(Report { ... }) is success, while Err(...) is an expected failure for the caller to handle. This Result is not an exception, and this enum does not represent every Rust error. Pattern matching makes Mode::Summary create an empty vector, Mode::LongWords(min) if min > 0 retain words of at least min characters, and Mode::LongWords(_) return Err(AnalyseError::ZeroLimit).
Ownership before lifetimes: move, borrow, and string slices
Start with an owned value:
let text = String::from("rust makes ownership explicit");
let moved = text;Ownership moves to moved, so using text afterwards is rejected. Borrow instead when the caller must keep its value:
let text = String::from("rust makes ownership explicit");
let borrowed: &str = &text;Both borrowed and text remain readable during the shared borrow. In the program, main owns the String, analyse(&text, ...) borrows a &str, trim() returns a borrowed &str, and Vec<&str> holds borrowed slices. The returned Vec<String> owns its three matches, so Report does not borrow from the local words vector. The compiler infers these relationships, requiring no explicit lifetime annotation.
For a comparison with lower-level programming foundations, the C Language course is an optional next step. C and Rust do not enforce the same memory model.

Build the first Rust program end to end
Place this program in src/main.rs of a local Rust project, run it, and compare:
#[derive(Debug)]
struct Report {
words: usize,
chars: usize,
matches: Vec<String>,
}
enum Mode {
Summary,
LongWords(usize),
}
#[derive(Debug)]
enum AnalyseError {
EmptyInput,
ZeroLimit,
}
fn analyse(text: &str, mode: Mode) -> Result<Report, AnalyseError> {
let trimmed = text.trim();
if trimmed.is_empty() {
return Err(AnalyseError::EmptyInput);
}
let words: Vec<&str> = trimmed.split_whitespace().collect();
let matches = match mode {
Mode::Summary => Vec::new(),
Mode::LongWords(min) if min > 0 => words
.iter()
.filter(|word| word.chars().count() >= min)
.map(|word| (*word).to_string())
.collect(),
Mode::LongWords(_) => return Err(AnalyseError::ZeroLimit),
};
Ok(Report {
words: words.len(),
chars: trimmed.chars().count(),
matches,
})
}
fn main() {
let text = String::from("rust makes ownership explicit");
match analyse(&text, Mode::LongWords(5)) {
Ok(report) => println!(
"words={}, chars={}, matches={:?}",
report.words, report.chars, report.matches
),
Err(error) => println!("error: {:?}", error),
}
println!("original={}", text);
}The successful output is exactly:
words=4, chars=29, matches=["makes", "ownership", "explicit"]
original=rust makes ownership explicitThe second line works because the call borrowed text.
Trace the worked values through the program
The input has no outer whitespace. Its word lengths are 4 + 5 + 9 + 8 = 26, and the three spaces bring the character count to 26 + 3 = 29. split_whitespace() produces "rust" (4), "makes" (5), "ownership" (9), and "explicit" (8). With >= 5, the last three enter matches.
value | type | owner or borrower | value after the call |
|---|---|---|---|
|
| owned by | unchanged |
|
| borrows | borrow ended |
|
| vector owned locally, elements borrow | dropped |
|
| owned strings moved into | returned |
|
| owned numeric value |
|
|
| owned numeric value |
|
Boundary checks expose each branch. Mode::LongWords(9) returns only ["ownership"]. Mode::LongWords(0) returns Err(AnalyseError::ZeroLimit). Three spaces become empty after trim() and return Err(AnalyseError::EmptyInput).

Beginner traps: moves, counting, cloning, and unchecked errors
If analyse accepted text: String, the call would consume the value. text: &str borrows it. Using .clone() merely to silence a move creates another allocation. Clone for a genuinely independent copy, not as the automatic borrow-checker repair.
Changing >= min to > min wrongly excludes "makes" at threshold 5. Also, str::len() counts UTF-8 bytes, while chars().count() counts Unicode scalar values. For "café", len() == 5 bytes and chars().count() == 4. Neither measure counts user-perceived grapheme clusters.
The exhaustive match handles Ok and Err, so these expected failures do not panic here. unwrap() is not forbidden, but it is unsuitable when errors are expected and recoverable.
How coding exercises and interviews test these Rust ideas
Useful exercises test the model, not a claimed official pattern:
Does
let second = text; println!("{}", text);compile? No. The assignment moves ownership tosecond, so the later use oftextis rejected.What is the threshold-
9result? It is["ownership"].Add
Mode::FirstWord. Thematchmust gain aMode::FirstWordarm to remain exhaustive.
Next, change Report.matches to Vec<usize> and return lengths at threshold 5. Expect [5, 9, 8]. These numbers are owned values, so no cloned words are needed. A C++ learning path can provide syntax comparison, but Rust ownership remains the focus. These are informal practice drills rather than exam questions, so they carry no marks or fixed pattern.
Short version and the next program to write
Keep this mental model:
Bindings are immutable unless marked
mut.Every owned value has one owner at a time, and
&Tborrows without taking ownership.Structs collect related fields.
Enums plus
matchmodel alternatives.Resultmakes success and failure explicit.
Now analyse "red blue red green" into a word-frequency map. Acceptance checks are red=2, blue=1, green=1, and the original input must still print after analysis. For more small cross-language exercises, try Classic Programs in C, Java and Python.
For broader placement-oriented practice, the Coding for Placements course covers C, C++, Java, Python, and competitive coding, not Rust.
Keep learning

Types of Programming Languages MCQs: 12 Solved Classification Questions
Work through 12 solved programming-language classification questions. Each solution identifies the deciding axis, checks the options and explains the main trap.

Classes and Objects MCQs: 12 Solved OOP Questions with Explanations
Solve 12 classes and objects MCQs, check each explanation, and use a worked Java trace to separate instance state, constructors, overloading and nested classes.

Programming Language Syntax MCQs: 12 Solved Questions on Grammar, Tokens and Semantics
Solve 12 MCQs on syntax, tokens, pseudocode, C grammar and runtime behaviour. Each explanation identifies the rule and applies it.

Imperative Programming Structure and Flow Control MCQs: 12 Solved Questions
Test whether you can trace changing state, choose the right loop, read a flowchart, and follow event-driven or exceptional control paths through 12 solved MCQs.