I'll continue my Rust Learning Series and this time I'll continue talking about the basics, more specifically, functions. But first, I'd like to bring up a very good VS Code extension to work with Rust, the "rust-analyzer"
β οΈ Remember!
You can find all the code snippets for this series in its accompanying repo
If you don't want to install Rust locally, you can play with all the code of this series in the official Rust Playground that can be found on its official page. (cargo commands won't run though. But you mostly won't be needing it)
β οΈβ οΈ The articles in this series are loosely following the contents of "The Rust Programming Language, 2nd Edition" by Steve Klabnik and Carol Nichols in a way that reflects my understanding from a Python developer's perspective.
β I try to publish a new article every week (maybe more if the Rust gods π are generous π) so stay tuned π. I'll be posting "new articles updates" on my LinkedIn.
Table of Content
The rust-analyzer VS Code extension:
Rust is hard to learn, I get it. But the good news is that there are tools out there that can make it easier! π
If you are using VS Code, rust-analyser is a wonderful companion to your Rust learning journey π. I highly recommend installing this extension while learning Rust (and when you are done learning too). As listed in its manual, rust-analyzer "is a library for semantic analysis of Rust code as it changes over time." Think of it as your Rust peer developer that has your good Rust coding form in heart π₯°. It can do a lot of stuff, like:
- Code completion with imports insertion
- Go to definition, implementation, type definition
- Find all references, workspace symbol search, symbol renaming
- Types and documentation on hover
- Inlay hints for types and parameter names
- Semantic syntax highlighting
- A lot of assists (code actions)
- Apply suggestions from errors
- And many more...
Highly recommended π
Functions:
You may not know it, but you know how to define a function in Rust by now π. Remember the main
function?!
Function in Rust are defined with the fn
keyword. Like Python, functions can be called from other functions:
fn main() {
println!("Main Function");
// A function call
second_function();
fn second_function() {
println!("Second Function");
}
Here, we called second_function
from the main function and the output should be like this:
Compiling functions v0.1.0 (/mnt/c/Users/fady/code/03-the-basics-functions-and-flow-control/functions)
Finished dev [unoptimized + debuginfo] target(s) in 1.17s
Running `target/debug/functions`
Main Function
Second Function
Parameters:
Like other languages, functions in rust accept parameters
, arguments
or whatever what you call the values passed to it π€·ββοΈ. But in Rust, parameters must have a defined type to eliminate the guess work for the Rust compiler.
fn main() {
// A function with parameters
print_temperature(35, 'C');
}
fn print_temperature(value: i32, unit: char) {
println!("The temperature is {value}{unit}");
}
The output this time will be:
The temperature is 35C
Statements and Expressions:
While in Python land, I didn't even bother to know the distinction between statements and expressions, who cares?! π
But in Rust, you have to know the difference and you will see why in the next section.
In short, "Statements" don't return a value and "Expressions" do and you can turn any expression into a statement by terminating it with a semi-colon ";", that's it! π
(Technically, statements return the "unit" aka THE VOID π―)
Here is an example:
fn main() {
// Statements and Expressions
// let y = (let z = 10); // Error: expected expression, found statement
let x = 10; // Statement
println!("The values of x is {x}");
let y = {
let z = 10;
z + 13
}; // The whole block is an expression
println!("The value of y is {y}");
}
The let
statement is a classic example of a Rust ... well ... statement π. It doesn't return anything. So the following would error out let y = (let z = 10);
as (let z = 10)
is a statement and you can't assign statements to statements! This makes my brain hurt π€!
For y
, notice the absence of the semi-colon ";" after z + 13
in the block. This (as we will see next) is the return value of the block and the whole block becomes an expression as it produces a "value". When you execute this code, you will see that the value of y is 23
.
Return values:
You've probably guessed it from the previous section π. Functions can have explicit return value which is the "last expression in the function block". But like parameters, you must define the type of the function's return value. We use a similar syntax to Python's "type hinting". To define the return's value type, we use ->
then the desired type.
Here is an example:
fn main() {
//Return Values
let number_five = five();
println!("number_five's value is {number_five}");
let add_one = add_one(7);
println!("The value of add_one is {add_one}")
}
fn five() -> u8 {
5
}
fn add_one(value: i32) -> i32 {
// value + 1; // Error (notice the semi-colon). The function is expecting a i32 return type but got unit ()
value + 1
}
For the five()
function, we return the number 5
(notice the absence of the semi-colon which makes this an expression). 5
can be safely casted into an "8-bits unsigned integer" type, hence the -> u8
after the parentheses. If we rewrite the five()
function as the following, it will error out:
fn five() -> u8 {
5; // adding a semi-colon.
}
Here we are telling the Rust compiler that we are expecting an "8-bit unsigned integer" but the semi-colon transformed the last expression in the function's block into a statement, hence this function now returns "the unit ()".
That's a quick overview on Functions in Rust! In the next article, we will continue our discussion about flow control (if statement and loops). See you then π
Top comments (0)