C++ vs. Rust: Factorial

Let’s dig a little bit in some implementations (in C++ and Rust) of a function that computes the factorial of an integer passed as argument. I will try to make them as similar as possible and will expose the differences between both:

Recursive factorial

This is the simplest and most known factorial implementation:

int factorial(int n)
{
    if (n <= 1)
        return 1;

    return n * factorial(n - 1);
}

Now, the “same” implementation in Rust:

fn factorial(n : i32) -> i32
{
    if n <= 1
    {
        return n;
    }
        
    return n * factorial(n - 1);
}

Though similar and probably producing the same binaries, there are very interesting differences to take into account:

  1. All functions in Rust start with the “fn” keyword. In C++ they start with the function return type, void, or auto.
  2. In Rust you must specify the return type after ->. Since C++11 you can do the same if you mark your method as “auto“. If you do not specify the return type, the function does not return anything (like a C++ void function).
  3. The Rust type “i32” refers to a 32-bit integer. In C++ “int” represents an integer that could have (as far as I know, all current implementations have a 32-bit integer called: “int“) a 32-bit representation. This could be not true for old platforms, compilers or very small ones where the int could be 16-bit. Having an integer with well-defined size for all platforms make code portability easier. (C++ also have the int32_t alias, but is not an actual type).
  4. Rust’s “if” discourages the usage of parenthesis in the expression to evaluate.
  5. Rust mandates the “if” and “else” blocks will be enclosed with curly braces.

Non-recursive implementation

C++ version, using “while“. I am not using “for” because the C/C++/Java/C# -like “for” does not exist in Rust.

int nonRecursiveFactorial(int n)
{
    int r = 1;
    
    while (n >= 1)
    {
        r *= n;
        n--;
    }

    return r;
}

And now, the same code in Rust:

fn non_recursive_factorial(mut n : i32) -> i32
{
    let mut r = 1;
    
    while n >= 1
    {
        r *= n;
        n -= 1;
    }
    
    return r;
}

Again, interesting differences:

  1. I called “nonRecursiveFactorial” my function in C++ and “non_recursive_factorial” my function in Rust. Though I can call my functions whatever I want, the Rust compiler suggests me to use snake_case instead of camelCase.
  2. Notice I marked my argument as “mut” and my variable rmut” as well. “mut” stands for “mutable” and means that the value of that variable can be modified in its lifetime. All variables in Rust are immutable by default (similar to a C++ const variable) and that simple feature removes a lot of concurrency problems.
  3. Again, while does not have parenthesis in its expresion.
  4. Notice I am writing n -= 1; instead of n--; in Rust. Rust does not have “++” or “--” operators because their existence would make lifetime management complicated and the code with those operators can be hard to read in some scenarios.

I want to use “for” anyway

C++ version:

int factorialWithFor(int n)
{
    int r = 1;
    
    for (int i = 2; i < n + 1; i++)
        r *= i;

    return r;
}

Rust version:

fn factorial_with_for(n : i32) -> i32
{
    let mut r = 1;
    
    for i in 2..n + 1
    {
        r *= i;
    }
    
    return r;
}

Once more, interesting differences:

  1. The “for” loop in Rust is a range-based-for-loop, similar to the C++11 range-based-for-loop or C# foreach.
  2. The variable i inside the loop is mutable, it is declared and lives only in that block.
  3. After the “in” Rust keyword, I wrote “2..n+1“. That is the Rust way of creating a range of values between [2; n + 1[ (so the loop will run until n only).
  4. If I would want to have a countdown instead, I could write “(2..n + 1).rev()” instead, that would downcount from n to 2.

Until now, very nice language indeed.

Advertisement

C++ vs. Rust: Hello World

This is my first program in Rust, obviously, a “Hello World“! :)

Two ways of creating it:

1. Everything manually

a. Need to create a file with .rs extension. In my case: HelloWorld.rs

b. Write the actual program in that file:

// Hello World in Rust

/* 
 * Same multiline comment like in C
 */

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

c. Go to the command line and compile the file with the rustc compiler

rustc HelloWorld.rs -o HelloWorld

d. Execute the binary file

./HelloWorld
Hello world

2. Using cargo.

a. cargo is the Rust package manager that helps you managing dependencies and also is useful in the build process. You can use it to create your program and compile it. So, we can create a new “cargo package“:

cargo new HelloWorld

b. That creates a new “cargo package” called “HelloWorld“: cargo new creates a new “HelloWorld” directory, that contains a Cargo.toml descriptor file and a src directory that contains a main.rs skeleton file that already contains a “Hello World” project similar to the one I wrote above:

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

c. To compile the package, we can build it using cargo too:

cd HelloWorld
cargo build

d. If everything is ok, a new directory called target is created and inside it, directories for debug or release builds (cargo build --release). Going to the debug folder we can run the HelloWorld executable.

3. “Hello World” content

The comments are similar to the C-like languages: // for simple line and /* */ for multiline comments.

The “fn” keyword identifies a block as a Rust function. All functions in Rust have a name and a set of arguments. The return type will be deduced automatically by the compiler.

main“, as in C, is the program entry point and is the function that is executed when you invoke the program from the command line.

println!” is a Rust macro that prints a line of the text specified. A Rust macro is a piece of code able to generate code by itself (metaprogramming).

This is it for now. I will continue writing about Rust while learning it. Thanks for reading!

4. Comparison with C++

This is the most similar implementation of “Hello world” in C++:

#include "fmt/core.h"

int main()
{
  fmt::print("Hello world\n");
}

I used libfmt to print the "Hello world” text to make both implementations as similar as possible.

Notice that Rust does not need any #include stuff. Actually Rust lets you import libraries in a modern way (similar to Java imports or C++20 modules) but println! is a macro included in the standard library, imported by default.

Function main() is also the program entry point in Rust, but it does not return anything, in C++, it MUST return an int, that, if not explicitly mentioned, it will return 0.