Reading — step 1 of 5
Learn
Functions
Rust functions look like functions everywhere — until you notice what's missing at the end of them. That missing semicolon is a real language feature, it's idiomatic everywhere, and misunderstanding it produces Rust's most-Googled beginner error. Five minutes here saves hours later.
The shape
fn add(a: i32, b: i32) -> i32 {
a + b
}
- Parameter types are required — no inference in signatures, by design: a function's signature is its contract, and contracts are spelled out.
- The return type follows
->. - Naming:
snake_case, enforced by compiler warnings.
Expressions vs statements: the semicolon rule
The body above ends with a + b — no semicolon — and that's the return. The rule:
- An expression without a semicolon at the end of a block is the block's value.
- Adding a semicolon turns an expression into a statement that evaluates to
()— Rust's "nothing" type.
So this fails, instructively:
fn add(a: i32, b: i32) -> i32 {
a + b; // ← semicolon!
}
// error[E0308]: mismatched types — expected `i32`, found `()`
// help: remove this semicolon
Read that error once, carefully, and you'll recognize it forever: "expected i32, found ()" almost always means a stray semicolon on the last line. The compiler even points at it.
return exists — for early exits:
fn classify(n: i32) -> &'static str {
if n < 0 {
return "negative"; // bail early: use return
}
if n == 0 { "zero" } else { "positive" } // final value: no semicolon
}
Idiomatic Rust reserves return for early exit and lets the final expression fall out the bottom. Since if and match are expressions, whole functions are often a single expression — that classify has no return on its main path and no temporary variable, and that's the house style.
Values in, value out
Arguments pass by value — for simple Copy types like integers, that's a plain copy; mutating a parameter inside doesn't touch the caller's variable. (For String and Vec, passing by value moves ownership — the subject of the next chapter's big reveal; today's exercises stick to integers precisely so you learn the shape first.)
No return type means the function returns ():
fn greet(name: &str) { // returns () implicitly
println!("Hi, {name}");
}
Your exercise: Square It
Define fn square(n: i64) -> i64, read a number in main, print square(n).
fn square(n: i64) -> i64 {
n * n // expression return — no semicolon, no `return`
}
fn main() {
let n: i64 = read_input(); // your stdin pattern from earlier lessons
println!("{}", square(n));
}
Keep the discipline from the Go track if you've been there — and it's doubly idiomatic here: logic in functions (pure, testable), I/O in main (glue). Rust's expression orientation makes the logic functions satisfyingly terse; let them be terse, and let main do the plumbing.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…