DEV Community

Cover image for The Rust Journey of a JavaScript Developer • Day 3
Federico Moretti
Federico Moretti

Posted on

The Rust Journey of a JavaScript Developer • Day 3

Back to basics. Third chapter of The Rust Programming Language is about common concepts, such as variables, types, functions, and comments: we’ve already seen all of them in the previous episode, but I’ll go deeper this time. Here’s the full list of primitives you must be aware of.


Rust, like any other programming language, has some reserved keywords that you can’t use to name variables, etc.; the book lists in an appendix all the keywords currently in use or that could be used in future releases. What’s interesting here is the raw identifier r# which let you use them anyway like r#keyword.

Variables and Mutability

I’ve already noticed it last time: variables are immutable by default in Rust, even though they are all called let. This can be confusing, coming from JavaScript, where we distinguish let from const. In fact, here let equals to const and let mut equals to let. The mut reserved keyword stands for “mutable”.

fn main() {
    let x = 5;
    println!("The value of x is: {x}");
    x = 6;
    println!("The value of x is: {x}");
}
Enter fullscreen mode Exit fullscreen mode

No matter its type, the function above will fail to compile, because x can’t be reassigned. You should receive a cannot assign twice to immutable variable ‘x’ error if you try to: you must set x as a mut variable to assign it once again after the first declaration, otherwise it will be considered as immutable.

Constants

Yes, not only Rust uses let as it was const in JavaScript, if you don’t specify mut, but it also has its own const. Google in a very old and deprecated version of its JavaScript Style Guide proposed to set them as Rust does: that is, in uppercase. But what’s the difference between immutable let and const?

const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
Enter fullscreen mode Exit fullscreen mode

In Rust, const are not only immutable, because they also require type annotations, while let doesn’t. Then, if you want to set a constant, you must declare its type as well: there’s yet another difference that matters. Constants can’t be “shadowed” and are valid within their initial scope.

Shadowing

Speaking about scopes, Rust is closer to JavaScript when it comes to assigning variables. While const can’t, let can be both “scoped” and “shadowed”: two ways of declaring the same keyword several times with different values. Keep in mind that you can’t just reassign a value, without a new declaration.

fn main() {
    let x = 5;

    let x = x + 1;

    {
        let x = x * 2;
        println!("The value of x in the inner scope is: {x}");
    }

    println!("The value of x is: {x}");
}
Enter fullscreen mode Exit fullscreen mode

There’s yet another thing to say about let variables. Even though you set them as mut, you can’t change their type: you can only do this by shadowing them, otherwise you’ll get a compilation error. So, for example, if x is a string, you can’t assign it to a number, unless you shadow it.

Data Types

Rust is far more complicated than JavaScript about data types. Starting from numbers, it provides more options to identify them: that’s because it includes interfaces à la TypeScript to better set variables. Let’s start with something that doesn’t change, namely the scalar types.

Scalar Types

These types are integers, floating-point numbers, booleans, and characters like other programming languages. While these kinds would be sufficient on their own, Rust goes further by dividing them into subtypes: you should refresh your computer science basics to understand their meaning.

Integer Subtypes

Starting from integers, there’s a table which shows all the possible subtypes you can use. You must determine the range of the number in bits beforehand, since variables and constants that need a type annotation must fit the right subtype. I know, it can be tricky.

Length Signed Unsigned
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
128-bit i128 u128
architecture dependent isize usize

I wasn’t joking when I said you should brush up on computer science. I don’t want to explain what the individual symbols mean here, but in general you shouldn't have any major problems using the default value, which is i32. You can also write integers as literals or use the underscore _ as a thousand separator.

Floating-Point Subtypes

Fortunately, floating-point numbers have only two subtypes, f32 and f64. They follow modern systems architecture: I don’t think that you’ll ever need to use something different from the default f64 subtype today, but I wanted to list both. Rust complexity on types ends here.

Boolean Type

There isn’t much to say about Booleans: they can either be true or false and their type annotation in Rust is bool. Did you expect something more complicated? Not now, at least… I already had enough with numbers. We’ll come back to Booleans in a while, because they’re useful for conditional expressions.

Character Type

Two or more characters make up a string, but they can be also considered individually. Rust require single quotes to define a char: strings use double quotes—you can’t choose on your own. I think this is great, because we have both a 4-space indentation and a quotes policy which act as a consistent coding style guide.

Functions

Over the years, JavaScript has introduced several ways to define a function. On the other hand, Rust has a stricter approach that is more similar to Python: we must pay attention to the details here. First, everything is executed within the main() function in the order in which it appears.

fn main() {
    println!("Hello, world!");

    another_function();
}

fn another_function() {
    println!("Another function.");
}
Enter fullscreen mode Exit fullscreen mode

This will print Hello, world! followed by Another function. in a new line. We should use snake case as a naming convention for functions, and order them accordingly: fn equals to Python def, but we do have curly braces and semicolons. Let’s see how they work.

Parameters

Functions may have parameters, and these must have type annotations. You can’t add a parameter without: yet another choice I appreciate, being a TypeScript developer, since it helps to understand what a function expect, when called. But we’re going to see something “weird”.

fn main() {
    another_function(5);
}

fn another_function(x: i32) {
    println!("The value of x is: {x}");
}
Enter fullscreen mode Exit fullscreen mode

As you can see, the second function is executed from main(). It doesn’t really matter if you put additional functions above or below it, since the order of execution is computed inside the curly braces. Notice the ending semicolon after the macro: it will disappear with returning values.

Return Values

There are two kinds of function in Rust. They’re not that different between what we have in JavaScript, but their syntax changes: if you need to actually return a value, instead of performing other actions, you must omit the ending semicolon. Again, if your function only returns a value, you must add an arrow.

fn five() -> i32 {
    5
}

fn main() {
    let x = five();

    println!("The value of x is: {x}");
}
Enter fullscreen mode Exit fullscreen mode

It makes sense, but it can be confused with JavaScript arrow functions, and I didn’t know anything about the semicolon: Rust will fail to compile if you append it by mistake, but luckily the compiler will warn you about the issue. We will come back to functions very soon.

Comments

Rust uses the same syntax as JavaScript for comments, so you can easily switch between the two languages. We have two slashes // for inline commenting, and a mix of slashes and asterisks /* */ for multi-line. No need to learn something new this time.

Control Flow

The control flow is, more or less, the same we have in most programming languages. It’s just a matter of using the right syntax—from conditionals to loops. This will be the latest topic for the episode, which has been pretty long so far: I think that further investigations will be required in the near future.

if Expressions

Let’s start with if. I can’t see many differences from what I’m used to. We haven’t talk about operators already, but they seem to be easier: I didn’t find truthy and/or falsy values here, so I only see a matter of indentation and syntax. They’re not that new as well.

fn main() {
    let number = 6;

    if number % 4 == 0 {
        println!("number is divisible by 4");
    } else if number % 3 == 0 {
        println!("number is divisible by 3");
    } else if number % 2 == 0 {
        println!("number is divisible by 2");
    } else {
        println!("number is not divisible by 4, 3, or 2");
    }
}
Enter fullscreen mode Exit fullscreen mode

Above, else if and else statements are optional. The if expression works the same way as in JavaScript, but it lacks the parenthesis: its result must be a bool. There’s also a sort of ternary operator with combines let and if. Again, we have a different way of defining it.

fn main() {
    let condition = true;
    let number = if condition { 5 } else { 6 };

    println!("The value of number is: {number}");
}
Enter fullscreen mode Exit fullscreen mode

It returns 5, since condition is true, otherwise it would have returned 6. Be aware that the values between curly braces must be of the same type: you can’t assign either a number or a string. Notice that they also don’t end with a semicolon, since they’re return values like in a function.

Loops

Rust offers three kinds of loop that resemble those in JavaScript, but with an exception. First, we have a loop which works like a while: if conditions are missing, it can be infinite, then use it with cautions. You can stop the loop and exit with a break and an optional value to be returned.

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    };

    println!("The result is {result}");
}
Enter fullscreen mode Exit fullscreen mode

If you want to exit the whole function instead, return is required. While loop don’t have to be set as a variable, it often is: and there’s another interesting feature of Rust that is worth talking about. Loops can have labels, so you may want to break a specific loop if multiple are provided.

fn main() {
    let mut count = 0;
    'counting_up: loop {
        println!("count = {count}");
        let mut remaining = 10;

        loop {
            println!("remaining = {remaining}");
            if remaining == 9 {
                break;
            }
            if count == 2 {
                break 'counting_up;
            }
            remaining -= 1;
        }

        count += 1;
    }
    println!("End count = {count}");
}
Enter fullscreen mode Exit fullscreen mode

This is a new syntax for me: 'counting_up represent the loop’s label, but it doesn’t require the quotes to be closed. I can’t imagine a situation where this kind of labels could help me right now: anyway, I think it’s better to know that this option do exists. Wait, there’s more!

fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{number}!");

        number -= 1;
    }

    println!("LIFTOFF!!!");
}
Enter fullscreen mode Exit fullscreen mode

We also have regular while loops. Notice that involved variables must be mutable in case of loops, otherwise you’ll get a compilation error, because they need to be reassigned every time—and reassignments must be of the same type. These are basic concepts, but there are many of them.


Loops can be defined with for as well, but we didn’t take collections and ranges into considerations already: I prefer to talk about it later, when I will have a deeper knowledge of them. This episode took three days to be completed, and I think that it includes lots of information, maybe too much.

Top comments (0)