Functions and Comments
Functions
Functions in Rust are conceptually similar to those in most programming languages:
They are named blocks of code that perform a specific task.
There’s no need to redefine what a function is in detail here — the Rust Book already covers it clearly: https://doc.rust-lang.org/book/ch03-03-how-functions-work.html#functions
Instead, here are the key Rust-specific points:
- The
mainfunction is required for a binary program. It acts as the entry point. - The
fnkeyword is used to declare functions. - Rust uses
snake_caseby convention for function and variable names. - The location of function definitions in the file does not matter.
Example:
fn greet() {
println!("Hello!");
}
fn main() {
greet();
}
Notice that greet could appear above or below main, it would still compile.
How can Rust allow functions to be defined anywhere?
Rust does not execute code as it reads it from top to bottom.
Instead, during compilation, Rust:
- Parses the entire file
- Collects all function signatures
- Builds an internal representation of the program
- Then performs type checking and validation
By the time Rust checks main, it already knows that greet exists.
In other words:
The compiler has global knowledge of the function definitions within the module before it validates individual calls.
This is different from some interpreted or scripting languages that evaluate code strictly in execution order.
Parameters
Similar to most programming languages, Rust functions can take parameters.
A parameter is:
A variable that is part of a function’s signature and receives a value when the function is called.
Example:
fn main() {
another_function(5);
}
fn another_function(x: i32) {
println!("The value of x is: {x}");
}
In Rust, every parameter must have an explicit type.
This is important because:
- Rust is statically typed.
- Function signatures are part of the contract of the function.
- The compiler does not guess the intended type of a parameter.
Being explicit here improves clarity and avoids ambiguity.
How is a value passed into a function?
When a function is called, the argument is passed into the function parameter.
As previously mentioned, in Rust, this can happen in three different ways:
- Copy
- Move
- Borrow (by reference)
In the example above:
fn another_function(x: i32)
The value 5 is passed by copy, because i32 implements the Copy trait.
That means:
- A duplicate value is created.
- The original value remains usable.
Later, when we discuss ownership in more detail, we will see that:
- Types like
Stringdo not implementCopy. - Passing them to a function may transfer ownership (a move).
- To avoid transferring ownership, we pass them by reference (
&T).
For now, just remember:
Primitive numeric types are copied.
More complex types may move unless explicitly borrowed.
Statements and Expressions
This is actually one of the most important ideas in Rust.
The book states:
Function bodies are made up of a series of statements optionally ending in an expression.
That single sentence explains a lot about how Rust works.
What’s the difference?
-
Statements
- Perform an action
- Do not return a value
- Usually end with a semicolon (
;)
-
Expressions
- Evaluate to a value
- Do not end with a semicolon
This distinction matters more in Rust than in many other languages.
How this works in C:
In C, many things are expressions — even assignments.
int v = 1337;
This is a declaration statement.
But assignment itself is an expression in C:
int x;
int y = (x = 5); // valid in C
Here:
x = 5assigns5tox- The assignment expression evaluates to
5 - That value is then assigned to
y
So in C, assignment both:
- Performs an action
- Produces a value
Rust deliberately does not work this way.
How this works in Rust
In Rust:
let y = 6;
This is a statement. It creates a binding, but it does not evaluate to a value.
That’s why this does not work:
let x = (let y = 6);
Because:
let y = 6is a statement.- Statements do not produce a value.
- But
let x = (...)expects something that evaluates to a value.
So the compiler rejects it.
A useful mental model
Think of it like this:
- Statements change the program’s state
- Expressions compute values
In Rust, if you want something to produce a value, it must be an expression.
For example:
let x = {
let y = 6;
y + 1
};
That value becomes the value of x.
This difference is important in Rust because:
if,match, and{}blocks are expressions (we will discuss more about control flow later on)- The last line of a function (without
;) becomes the return value - Adding or removing a semicolon can change the type of a function
This is not just syntax detail; it shapes how Rust programs are structured.
Functions with Return Values
Like other languages, Rust functions can return values to the caller.
However, Rust does this in a slightly different way.
Three key points:
- Return values are produced by the final expression
- The function signature must declare the return type using
-> - If a function declares a return type (
-> T), its body must evaluate toT. This usually means the final line is an expression (without a semicolon), unless you usereturn.
Example
fn five() -> i32 {
5
}
Here:
- The return type is
i32 - The final line
5is an expression - Because it has no semicolon, its value becomes the return value
What happens if we add a semicolon?
fn five() -> i32 {
5; // ❌ not allowed
}
This fails because:
5;is a statement- Statements do not return values
- The function is expected to return
i32 - But the body now evaluates to
()(unit type)
What if we remove the return type?
fn five() {
5;
}
This is allowed.
Because:
- When no return type is specified, the function implicitly returns
() 5;is a statement- Statements evaluate to
() - So the function body correctly evaluates to
()
Important:
- This function does not return
5. - The value
5is computed and immediately discarded.
Comments
Comments in Rust behave exactly as you would expect from most modern languages.
For full details, refer to the official documentation: https://doc.rust-lang.org/book/ch03-04-comments.html
In summary:
//— single-line comment///— documentation comment (used to generate API docs)//!— inner documentation comment (used for modules or crates)
Example:
// This is a regular comment
/// This is a documentation comment
fn add(a: i32, b: i32) -> i32 {
a + b
}
Rust encourages clear documentation, especially for public APIs.
The /// comments integrate with cargo doc to generate structured documentation automatically.