Functions
Functions are a fundamental element of any programming language. They are used to define a piece of code that can be executed multiple times, without having to rewrite the same code. Functions are used to break down a program into smaller, more manageable pieces.
In Rust, the naming convention for functions is to use snake_case
. This means that function names should be all lowercase, with words separated by underscores.
Functions in Rust are defined using the fn
keyword, followed by the function name, a list of parameters enclosed in parentheses, and a block of code enclosed in curly braces.
Let's take a look at a simple function in Rust:
In the example above, we have defined a function called greet
that prints Hello, World!
to the console. To call this function, we simply write its name followed by parentheses:
When we run the program, it will output Hello, World!
to the console.
Function Parameters
Functions are powerful when they can accept input values, called parameters. Parameters allow us to pass data to a function, which the function can then use to perform its task.
Parameters are defined inside the parentheses after the function name. Each parameter consists of a name followed by a colon and the parameter type. Multiple parameters are separated by commas. The Rust compiler expects the type of each parameter to be specified.
Here's an example of a function that takes two parameters:
In the example above, we have defined a function called add
that takes two parameters a
and b
, both of type i32
(a 32-bit signed integer). The function calculates the sum of the two parameters and prints the result to the console.
To call this function, we need to provide values for the parameters:
Add function
Expression vs Statement
The difference between an expression and a statement is that an expression evaluates to a value, while a statement does not.
In the function we defined earlier, the a + b
is an expression because it evaluates to a value, if we provide 5
for a
and 3
for b
, the expression will evaluate to 8
, therefore it is considered an expression.
Statements on the other hand do not evaluate to a value. For example, the let sum = a + b;
statement is not an expression because it does not evaluate to a value. It is a statement that assigns the value of a + b
to the variable sum
, but the statement itself does not evaluate to a value.
Example of a statement:
Example of an expression:
Statements perform actions without directly producing a value, while expressions evaluate to a value.
In Rust you can create a new scope using curly braces {}
. The last expression in a block is considered the return value of the block. This is useful when you want to declare variables in a block that doesn't pollute the outer scope.
New block expression
Function Return Values
Functions are more powerful and useful when they return a value. In fact, it's preferred for functions to return a value rather than mutating outside state. The returned value then can be used wherever the function is called.
We also need to explicitly specify the return type of a function. The return type is specified after an arrow (->
) following the parameter list. The return type is the type of the value that the function will return.
Let's change the add
function to return the sum of two numbers instead of printing it:
In the example above, we have changed the add
function by adding an arrow (->
) followed by i32
to specify that the function will return a 32-bit signed integer. The function now returns the sum of the two parameters a
and b
.
There is also a shorthand syntax for returning a value from a function. We can remove the return
keyword and the semicolon from the last expression in the function, and Rust will automatically return the value:
Remember that you also need to remove the semicolon from the last expression in the function. If you add a semicolon, it will turn the expression into a statement, and the function will not return a value.
To use the returned value, we need to assign it to a variable or use it directly:
This will output The sum of 5 and 3 is 8
to the console. Another way to do this is to directly use the returned value:
Pure Functions
Functions that do not modify external state and always produce the same output for the same input are called pure functions. In functional programming, pure functions are preferred because they are more predictable and they do not cause any side effects that could be difficult to debug.
Here's an example of a pure function (without side effects):
In the example above, we have defined a function called generate_sha_256_hash
that takes a string as input and returns a SHA-256 hash of the input string. This function is a pure function because it always produces the same hash as long as the input is the same.
To run the function add the sha256
crate by running cargo add sha256
in the terminal.
SHA256 generator Rust
Side Effects
Side effects are changes to external state that are caused by a function. Functions with side effects modify external state, such as writing to a file, or modifying a global variable. Side effects can make the behavior of a program harder to predict and debug.
Let's take a look at an example of a function with side effects:
In the example above, we have defined a function called write_to_file
that takes a string data
and a filename as input and writes the data to a file with the specified filename. This function has side effects because it modifies external state (the file system) by writing data to a file.
Function Overloading
Rust does not support function overloading in the traditional sense. Function overloading is the ability to define multiple functions with the same name but different parameter types or numbers of parameters. In languages that support function overloading, the compiler determines which function to call based on the number and types of arguments provided.
The language's powerful type system, including generics and traits, provides flexible ways to achieve similar outcomes without the ambiguity or complexity that can arise from traditional function overloading. We will be exploring these concepts in more detail in later chapters.
Conclusion
Functions are a fundamental building block of Rust programs. They allow us to break down a program into smaller, more manageable pieces, and they can accept input values and return output values. Functions can be used to perform specific tasks, and they can be called multiple times from different parts of the program.
In the next chapter, we will learn about comments and documentation in Rust. This will help us write clear and understandable code that is easy to maintain and share with others.