Rust Tutorial – Part 6 – Functions


Rust, Tutorials / Friday, December 8th, 2017

Functions in Rust are much like functions in conventional languages – simple entities that accept an input and produce an output (but not always). Here’s a very simple example of a function in Rust:

fn main() {
    simple_function(32);
}

fn simple_function(x: i8) {
    println!("The value received was {}", x);
}

So, we see that functions are defined using the keyword fn. You might have noticed that main() is also a function defined with fn. The value 32 is passed to the function, and it gets printed to the console. The only catch here is the type of the variable: Rust needs to know in advance the type of value that’ll be arriving in the function. I chose i8 because our example carries a really small integer. Generally, you’d want to use i32 unless memory is at a premium.

But what if we want our function to return a value? It can, as long as we tell Rust in advance what the return value’s type will be. Let’s see another example:

fn main() {
    let x = simple_function(10);
    println!("Got {} from function", x);
}

fn simple_function(x: i8) -> i8 {
    return x * 2;
}

The output, unsurprisingly, is Got 20 from function. We need to be careful when defining functions. If the outcoming value is larger than what the declared type can hold, things will blow up:

fn main() {
    let x = simple_function(10);
    println!("Got {} from function", x);
}

fn simple_function(x: i8) -> i8 {
    return x * 20000;
}

Now are multiplying x by 20000, and there’s no way the result can be contained in an 8-bit integer (i8). Let’s see what we get when we compile it:

warning: literal out of range for i8
  --> 01_variables_and_mutation.rs:14:16
   |
14 |     return x * 2000000;
   |                ^^^^^^^
   |
   = note: #[warn(overflowing_literals)] on by default

We’ve been warned; there’s going to be integer overflow. If we go ahead and run the program anyway, the sky falls down:

thread 'main' panicked at 'attempt to multiply with overflow', 01_variables_and_mutation.rs:14:12
note: Run with `RUST_BACKTRACE=1` for a backtrace.

There’s another trickery related to returning values, instead of saying return x * 2;, you can simply write x * 2 (note, no semi-colon):

fn simple_function(x: i8) -> i8 {
    x * 2
}

This works as intended. Why? Well, the details are a bit technical, but basically it’s the different between statements and expressions in a programming language. Statements do things, expressions return values. But the catch is that what’s expression in one programming language isn’t in another. So, something like this doesn’t work in Rust:

let y = (let x = 6);

This is an error, because let x = 6; doesn’t return a value (this will come as a major surprise to folks from C, Python, Java, Ruby, etc.). And that is so because x = 6; is considered a statement in Rust. It does something (Assigns 6 to x), but doesn’t return it. without the semi-colon, x * 2 is an expression and returns the value of x plus 2.

Is all this hair-splitting any practical use? Yes, if you ever want the value of one let to be returned to another variable:

fn main() {
    let y = {
        let z = 10;
        z + 2
    };

    println!("y is {}", y);
}

Looks odd, I’ll be honest, but it does what we need. We get y is 12 when we run the program.

A word of caution before we finish: using something like return z + 2; won’t work in the last example as the return statement marks the end of the code. I encourage you to try that and see what happens. 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.