Rust From the Ground Up — Part 2

Rust From the Ground Up — Part 2

Introduction

Hello again!

In the last post we’ve set up our environment to write some Rust code. Today we will finally see some stuff happening!

That’s enough, let’s see some code

As expected, let’s begin with last post’s Hello World application

fn main() {
  println!("Hello World!");
}

… And that’s it.

Rust is heavily inspired by functional languages such as Ocaml, so lets learn how to write some functions!

Functions

The language follows most of the common conventions of C based languages, with a nicer syntax in some places inspired by functional languages (especially Ocaml). For instance, as with C you can declare function, though you need to put the return type after the function name and parameter names:

fn meaning(x: bool) -> i32 {
  //…
}

But different from C when returning a value you can omit the “return” keyword and the last statement will be returned, such as in:

fn meaning(x: bool) -> i32 {
  42
}

This gets really interesting because, for instance, if is a statement. Therefore, what you might write as such in a C derived language

int meaning(bool x) {
  if (x) {
    return 42;
  } else {
    return 0;
  }
}

can be written as such in Rust

meaning(x: bool) -> i32 {
  if x {
    42
  } else {
    0
  }
}
meaning(true) // returns 42

This contrived example only shows a minor improvement, but it comes in handy on more complex scenarios and makes code using match statements (which will be shown in a future post) nicer to read. Something else of note is that comments start with // and goes until the end of the line. Rust does not have the concept of a block comment, therefore all lines of comment must start with //. In a future post we will talk about how to use comments to add documentation to your code.

If your function does not return any useful value it is said to return (), which is a Unit type

fn useless() -> () {
  println!("I'm useless!")
}

You can think of it as the void type in other languages. To make things simpler you can omit the return type for functions that return ()

fn useless() {
  println!("I'm useless!")
}

Statements

A statement is a component inside a block of code. Pretty much everything in Rust is a statement, including other blocks, making the definition somewhat recursive. Statements that finish with a ; return the Unit type. Like it was mentioned before, the last statement in a function will be the returned value of that function. This can lead to somewhat interesting constructs.

fn foo() -> i32 {
  40
}

fn bar() -> i32 {
  let foo_plus_one = {
  let foo_result = foo(); //the whole statement let foo_result = foo(); has type () because it ends with a ;
  foo_result + 1
  };
  foo_plus_one + 1 // if ; was added here, this statement would be of type (), which doesn't match the function's signature
}

println!(bar()) // prints 42

This pattern is useful once we get into the ownership model of Rust, which will be talked about in a future post. The keen reader might have noticed in the code sample the call to println includes a “!”, while the call to foo and bar does not. This is because “println” is not a function but a macro. Depending on how much experience you have in C and C++ you might just have had flashbacks of some interesting bug hunting. The macro system in Rust is smarter and safer than those, so you don’t need to be worried! We will also be the topic of a future post.

Declaring variables

In the latest code sample we can see the declaration of too variables, foo_plus_one and foo_result. By default variables in rust are immutable. The following code

let x = 5;
x = 6;

Will result in the following compiler error

error[E0384]: cannot assign twice to immutable variable `x`
 → src/main.rs:3:3
 |
2 | let x = 5;
 | -
 | |
 | first assignment to `x`
 | help: make this binding mutable: `mut x`
3 | x = 6;
 | ^^^^^ cannot assign twice to immutable variable

To make a variable mutable we introduce the mut keyword.

let mut x = 5;
x = 6; // this works fine

The control of mutability is a core concept in Rust and will be talked about in a future post. Something else the reader might have noticed is the lack of a type declaration in the variable. This does not mean that Rust is not a typed language! Rust is strongly typed, but types can be inferred most of the time. If you want to be explicit about the type of a variable you can introduce the type information in the function declaration:

let x: i32 = 5;

There are few places where you must define the type of the variables. The most common ones are the function’s return type and parameter types

fn foo(x: i32, y: i32) -> bool {
 …
}

What’s next?

I hope this first post was helpful as a basic introduction to the language’s syntax and building blocks. On the next post I’ll talk about the primitive types in rust, declaring your own types and how to handle control flow in the language.

See you then!