Practical Programming David Bouchet
Practical Work #5

Rust: Programming Basics

Submission

Due Date

By Friday 29 March 2019 23:42

Directory Hierarchy

Create your git repository (replace john.smith by your own login).

                
                    $ git clone git@git.cri.epita.net:p/2022-s4-tp/tp05-john.smith
                
            

It must contain the following files and directories:

Use only one directory per exercise and write all the files of an exercise in its associated directory. For instance, write all the files of the factorial exercise in the facto directory.

The AUTHORS file must contain the following information.

AUTHORS
                
                    First Name
                    Family Name
                    Login
                    Email Address
                
            

The last character of your AUTHORS file must be a newline character.

For instance:

AUTHORS
                
                    $ cat AUTHORS 
                    John
                    Smith
                    john.smith
                    john.smith@epita.fr
                    $ # Command prompt ready for the next command...
                
            

Be careful, if you do not follow all the given instructions, no point will be given to your answers.

Introduction

The purpose of this practical is to familiarize yourself with the basics of Rust's syntax, which differs from C's syntax.

The best way to do this is to write some functions you have already written in C. So, you will write the same functions again from a previous C practical: Programming Basics. But this time, use the Rust programming language.

For now, you are not allowed to use the high-level features of Rust (e.g. custom types, closures, traits, etc.).

For example, we can implement the facto() function this way:

                
                    fn facto(n: u64) -> u64
                    {
                        (2..=n).product()
                    }
                
            

Even if this implementation is right, it is not what you are supposed to do in this practical. Use only the common programming concepts of Rust.

Also, you are not allowed to use the two following keywords for all the exercises of this section:

To create a new binary application, you will use Cargo, which is Rust's package manager. By default, Cargo also creates a local git repository. In the context of this series of practicals, you must not create this repository because you are supposed to submit your work on a CRI's git repository.

To avoid any problem with your git repositories, you have to disable the feature of Cargo that creates a git repository.

To do so, you can use the --vcs none option each time you use the cargo new command or you can create the "~/.cargo/config" file with the following contents. (If this file already exists, add the two lines at the end of the file.)

~/.cargo/config
                
                    [cargo-new]
                    vcs = "none"
                
            

Also, when you compile an application with Cargo, it generates an executable file and some intermediate files. All of these files can take a large amount of disk space.

So, before you submit your code, you have to clean your directories.

For each project created with Cargo, you can use the following instruction:

                
                    $ cargo clean
                
            

Or you can manually delete all the target/ directories. Replace [PATH] by the directory used for this practical (e.g. ~/tp05-john.smith/pw_05_rust_basics/).

                
                    find [PATH] -type d -name target | xargs rm -rf
                
            

Each time you connect to one of the school's computers, you have to set the default configuration of Rust's tools. (You do not have to do this with your own computer.)

                
                    $ rustup default stable
                
            

Exercises

Factorial

You are going to write a program that works out the factorial of a number.

First, create a new project:

                
                    $ cargo new facto --vcs none
                
            

A new facto/ directory has been created. It contains the following files and directories.

                
                    $ tree facto/
                    facto
                    ├── Cargo.toml
                    └── src
                        └── main.rs
                
            

For now, ignore the Cargo.toml file. We will look at it in a moment.

The src/ folder is supposed to contain all your source files. It already contains a predefined source file that prints "Hello, world!".

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

Let us compile this code.

                
                    $ cd facto
                    $ cargo build
                       Compiling facto v0.1.0 (pw_05_rust_basics/facto)
                        Finished dev [unoptimized + debuginfo] target(s) in 2.89s
                    $ ls -F
                    Cargo.lock  Cargo.toml  src/  target/
                
            

Two new items have been created:

Note that you did not have to execute Rust's compiler directly (rustc). Cargo calls it for you. Actually, Cargo can be used to check, compile, execute and test your code in an easy way.

Now, let us run this code.

                
                    $ cargo run
                        Finished dev [unoptimized + debuginfo] target(s) in 0.01s
                         Running `target/debug/facto`
                    Hello, world!
                
            

As you can see, this program prints "Hello, world!". But before executing the program, Cargo also prints some information about the executable file. As you can see, it is executed in a debug mode by default. This mode is used during the development process. The compilation is faster, but the execution is slower. Also, the executable file contains some information that can be helpful for debugging. In a further exercise, we will see how we can execute a program in a faster mode.

So, now that you know how to compile and run a program, you are going to modify main.rs in order to write the facto() function.

First, let us look at the prototype of this function in the C language:

                
                    unsigned long facto(unsigned long n);
                
            

And the function's signature in Rust.

                
                    fn facto(n: u64) -> u64
                
            

The u64 type in Rust is equivalent to the unsigned long type in C.

For this first exercise, you will complete the following main.rs file.

main.rs
                
                    fn main()
                    {
                        // TODO
                    }

                    fn facto(n: u64) -> u64
                    {
                        // REMOVE THIS INSTRUCTION
                        n

                        // TODO
                    }

                    #[cfg(test)]
                    mod tests
                    {
                        use super::*;

                        #[test]
                        fn test_facto()
                        {
                            assert_eq!(facto(0), 1);
                            assert_eq!(facto(1), 1);
                            assert_eq!(facto(2), 2);
                            assert_eq!(facto(8), 40_320);
                            assert_eq!(facto(12), 479_001_600);
                            assert_eq!(facto(17), 355_687_428_096_000);
                            assert_eq!(facto(20), 2_432_902_008_176_640_000);
                        }
                    }
                
            

Replace the default main.rs file by the one above.

Compile this code.

                
                    $ cargo build
                       Compiling facto v0.1.0 (pw_05_rust_basics/facto)
                    warning: function is never used: `facto`
                     --> src/main.rs:6:1
                      |
                    6 | fn facto(n: u64) -> u64
                      | ^^^^^^^^^^^^^^^^^^^^^^^
                      |
                      = note: #[warn(dead_code)] on by default

                        Finished dev [unoptimized + debuginfo] target(s) in 0.43s
                
            

You have a warning on line 6 because your facto() function is never used. And that is true: your main() function does nothing.

Since no error occurs, an executable file has been generated and you can execute it. But as said previously, it does nothing.

                
                    $ cargo run
                        Finished dev [unoptimized + debuginfo] target(s) in 0.01s
                         Running `pw_05_rust_basics/facto/target/debug/facto`
                
            

Let us examine the implementation of facto(). It returns the contents of the n variable. So obviously, this function does not return the factorial of n.

Before writing your own implementation of facto(). Let us see the last part of the code: from line 14 to line 30.

This part is used to test the facto() function. Rust provides some facilities to test your code. It is a good practice to write the tests before the functions we want to test. This technique is called test-driven development (TDD).

For this first exercise, the test is given. So, let us examine it.

The annotation on line 14 tells the compiler that the following module contains unit tests and must be compiled and executed for testing only.

                
                    #[cfg(test)]
                
            

The line 15 defines a module named tests.

                
                    mod tests
                
            

We have not looked at the modules yet. So for now, consider that it is a way to structure your code.

The instruction on line 17 allows the test module to call all the functions defined in the outer module. In other words, you can call main() and facto() from the test module.

                
                    use super::*;
                
            

The annotation on line 19 tells the compiler that the following function must be executed when we run the tests.

                
                    #[test]
                
            

Then, we have the definition of test_facto(). This function will be used to test facto(). If the test fails, this function will panic. In Rust, we say that a code panics when it encounters an unrecoverable error and exits.

Here, the test is made up of assert_eq!() macros. If the two parameters of this macro are not equal, the program panics. So, the principle of this test function is very simple. It tests the return values of facto() for different arguments and compare them to the expected values. If a return value is not the factorial of the argument, the function panics and the test fails.

For instance, on line 22, if facto(0) returns a value different from 1, which is the factorial of 0, the assert_eq!() macro panics and the test fails.

                
                    assert_eq!(facto(0), 1);
                
            

Otherwise, the program continues with the next assert_eq!() macro that tests the factorial of 1. And so on and so forth...

Our test function should fail because the current facto() function is wrong. So, let us run the test.

                
                    $ cargo test
                       Compiling facto v0.1.0 (pw_05_rust_basics/facto)
                        Finished dev [unoptimized + debuginfo] target(s) in 0.23s
                         Running target/debug/deps/facto-fddb657be0fe7e51

                    running 1 test
                    test tests::test_facto ... FAILED

                    failures:

                    ---- tests::test_facto stdout ----
                    thread 'tests::test_facto' panicked at 'assertion failed: `(left == right)`
                      left: `0`,
                     right: `1`', src/main.rs:22:9
                    note: Run with `RUST_BACKTRACE=1` for a backtrace.


                    failures:
                        tests::test_facto

                    test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out

                    error: test failed, to rerun pass '--bin facto'
                
            

On line 7, we can see that our test has failed.

On lines 13 and 14, we have the left and right arguments that are compared by the assert_eq!() macro when it panics.

                
                      left: `0`,
                     right: `1`', src/main.rs:22:9
                
            

The macro that panics is on line 22 of the main.rs file.

                
                    assert_eq!(facto(0), 1);
                
            

These two values are different. Therefore, the macro panics and the test fails.

Now, modify the facto() function so that the test is successful. Your function should not print anything on the terminal.

The expected result is as follows:

                
                    $ cargo test
                       Compiling facto v0.1.0 (pw_05_rust_basics/facto)
                        Finished dev [unoptimized + debuginfo] target(s) in 0.25s
                         Running target/debug/deps/facto-fddb657be0fe7e51

                    running 1 test
                    test tests::test_facto ... ok

                    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
                
            

Once your test is successful, modify the main() function in order to print exactly (from line 5 to the end) the following output on the terminal:

                
                    $ cargo run
                       Compiling facto v0.1.0 (pw_05_rust_basics/facto)
                        Finished dev [unoptimized + debuginfo] target(s) in 0.25s
                         Running `target/debug/facto`
                    facto(0) = 1
                    facto(1) = 1
                    facto(2) = 2
                    facto(3) = 6
                    facto(4) = 24
                    facto(5) = 120
                    facto(6) = 720
                    facto(7) = 5040
                    facto(8) = 40320
                    facto(9) = 362880
                    facto(10) = 3628800
                    facto(11) = 39916800
                    facto(12) = 479001600
                    facto(13) = 6227020800
                    facto(14) = 87178291200
                    facto(15) = 1307674368000
                    facto(16) = 20922789888000
                    facto(17) = 355687428096000
                    facto(18) = 6402373705728000
                    facto(19) = 121645100408832000
                    facto(20) = 2432902008176640000
                
            

When you are done, do not forget to clean your directory.

                
                    $ cargo clean
                    $ tree
                    .
                    ├── Cargo.lock
                    ├── Cargo.toml
                    └── src
                        └── main.rs
                    
                    1 directory, 3 files
                
            

Fibonacci Sequence

The Fn Fibonacci sequence is defined by the following recurrence relation:

You are going to write a program that works out the Fibonacci numbers.

First, create the project:

                
                    $ cargo new fibo --vcs none
                
            

Replace the default main.rs file by this one:

main.rs
                
                    fn main()
                    {
                        // TODO
                    }

                    fn fibo(n: u64) -> u64
                    {
                        // TODO
                    }

                    #[cfg(test)]
                    mod tests
                    {
                        use super::*;

                        #[test]
                        fn test_fibo()
                        {
                            // TODO
                        }
                    }
                
            

Follow the steps below to complete the main.rs file.

  1. Implement fibo() so that your code compiles.
    • As we actually want the test to fail, your fibo() function is not meant to work properly and should not return the right values.
    • You just need your code to compile. To do so, you can use the unimplemented!() macro.
    • To check that your code compiles, you can use cargo check instead of cargo build. Using cargo check is faster because it does not generate the final executable file.
  2. Write the test for fibo(). In other words, complete test_fibo(). Use only asser_eq!() macros.
  3. Run the test. It should fail because the fibo() function is wrong. If the test does not fail, write it again.
  4. Write the fibo() function and test it. The test should pass. If the test fails, write fibo() again. Note that this function should not print anything on the terminal.
  5. Finally, write the main() function so that it prints exactly (from line 5 to the end) the following output on the terminal:
                
                    $ cargo run
                       Compiling fibo v0.1.0 (pw_05_rust_basics/fibo)
                        Finished dev [unoptimized + debuginfo] target(s) in 0.53s
                         Running `target/debug/fibo`
                    fibo(0) = 0
                    fibo(1) = 1
                    fibo(2) = 1
                    fibo(3) = 2
                    fibo(4) = 3
                    fibo(5) = 5
                    fibo(6) = 8
                    fibo(7) = 13
                    fibo(8) = 21
                    fibo(9) = 34
                    fibo(10) = 55
                    ...
                    fibo(81) = 37889062373143906
                    fibo(82) = 61305790721611591
                    fibo(83) = 99194853094755497
                    fibo(84) = 160500643816367088
                    fibo(85) = 259695496911122585
                    fibo(86) = 420196140727489673
                    fibo(87) = 679891637638612258
                    fibo(88) = 1100087778366101931
                    fibo(89) = 1779979416004714189
                    fibo(90) = 2880067194370816120
                
            

Finally, clean your directory.

                
                    $ cargo clean
                    $ tree
                    .
                    ├── Cargo.lock
                    ├── Cargo.toml
                    └── src
                        └── main.rs
                    
                    1 directory, 3 files
                
            

Integer Square Root

The integer square root of a non-negative integer n is the largest integer that is smaller than or equal to the square root of n.

To determine the integer square root (r) of a non-negative integer (n) you can use the Babylonian method:

You are going to write a program that works out the integer square root of a non-negative integer.

Complete the following main.rs file. Use the same method as in the previous exercise. Note that the isqrt() function should not print anything on the terminal.

main.rs
                
                    fn main()
                    {
                        // TODO
                    }

                    fn isqrt(n: u64) -> u64
                    {
                        // TODO
                    }

                    #[cfg(test)]
                    mod tests
                    {
                        // TODO
                    }
                
            

The expected result is as follows:

                
                    $ cargo run
                       Compiling isqrt v0.1.0 (pw_05_rust_basics/isqrt)
                        Finished dev [unoptimized + debuginfo] target(s) in 0.53s
                         Running `target/debug/isqrt`
                    isqrt(0) = 0
                    isqrt(8) = 2
                    isqrt(16) = 4
                    isqrt(24) = 4
                    isqrt(32) = 5
                    isqrt(40) = 6
                    isqrt(48) = 6
                    isqrt(56) = 7
                    isqrt(64) = 8
                    isqrt(72) = 8
                    isqrt(80) = 8
                    isqrt(88) = 9
                    isqrt(96) = 9
                    isqrt(104) = 10
                    isqrt(112) = 10
                    isqrt(120) = 10
                    isqrt(128) = 11
                    isqrt(136) = 11
                    isqrt(144) = 12
                    isqrt(152) = 12
                    isqrt(160) = 12
                    isqrt(168) = 12
                    isqrt(176) = 13
                    isqrt(184) = 13
                    isqrt(192) = 13
                    isqrt(200) = 14
                
            

Finally, clean your directory.

Powers of Two

You are going to write a program that works out a power of two as fast as possible (no iteration, no recursion).

Use the same method as previously. Complete the following main.rs file and clean your directory when you are done. Note that the power_of_two() function should not print anything on the terminal.

main.rs
                
                    fn main()
                    {
                        // TODO
                    }

                    fn power_of_two(n: u8) -> u64
                    {
                        // TODO
                    }

                    #[cfg(test)]
                    mod tests
                    {
                        // TODO
                    }
                
            

The expected result is as follows:

                
                    $ cargo run
                       Compiling power_of_two v0.1.0 (pw_05_rust_basics/power_of_two)
                        Finished dev [unoptimized + debuginfo] target(s) in 0.51s
                         Running `target/debug/power_of_two`
                    power_of_two(0) = 1
                    power_of_two(1) = 2
                    power_of_two(2) = 4
                    power_of_two(3) = 8
                    power_of_two(4) = 16
                    power_of_two(5) = 32
                    power_of_two(6) = 64
                    power_of_two(7) = 128
                    power_of_two(8) = 256
                    power_of_two(9) = 512
                    power_of_two(10) = 1024
                    power_of_two(11) = 2048
                    power_of_two(12) = 4096
                    power_of_two(13) = 8192
                    power_of_two(14) = 16384
                    power_of_two(15) = 32768
                    power_of_two(16) = 65536
                    power_of_two(17) = 131072
                    power_of_two(18) = 262144
                    power_of_two(19) = 524288
                    power_of_two(20) = 1048576
                    power_of_two(21) = 2097152
                    power_of_two(22) = 4194304
                    power_of_two(23) = 8388608
                    power_of_two(24) = 16777216
                    power_of_two(25) = 33554432
                    power_of_two(26) = 67108864
                    power_of_two(27) = 134217728
                    power_of_two(28) = 268435456
                    power_of_two(29) = 536870912
                    power_of_two(30) = 1073741824
                    power_of_two(31) = 2147483648
                    power_of_two(32) = 4294967296
                    power_of_two(33) = 8589934592
                    power_of_two(34) = 17179869184
                    power_of_two(35) = 34359738368
                    power_of_two(36) = 68719476736
                    power_of_two(37) = 137438953472
                    power_of_two(38) = 274877906944
                    power_of_two(39) = 549755813888
                    power_of_two(40) = 1099511627776
                    power_of_two(41) = 2199023255552
                    power_of_two(42) = 4398046511104
                    power_of_two(43) = 8796093022208
                    power_of_two(44) = 17592186044416
                    power_of_two(45) = 35184372088832
                    power_of_two(46) = 70368744177664
                    power_of_two(47) = 140737488355328
                    power_of_two(48) = 281474976710656
                    power_of_two(49) = 562949953421312
                    power_of_two(50) = 1125899906842624
                    power_of_two(51) = 2251799813685248
                    power_of_two(52) = 4503599627370496
                    power_of_two(53) = 9007199254740992
                    power_of_two(54) = 18014398509481984
                    power_of_two(55) = 36028797018963968
                    power_of_two(56) = 72057594037927936
                    power_of_two(57) = 144115188075855872
                    power_of_two(58) = 288230376151711744
                    power_of_two(59) = 576460752303423488
                    power_of_two(60) = 1152921504606846976
                    power_of_two(61) = 2305843009213693952
                    power_of_two(62) = 4611686018427387904
                    power_of_two(63) = 9223372036854775808
                
            

Digit Count

You are going to write a program that counts the number of digits in a base-10 non-negative integer.

Use the same method as previously. Complete the following main.rs file and clean your directory when you are done. Note that the digit_count() function should not print anything on the terminal.

main.rs
                
                    fn main()
                    {
                        // TODO
                    }

                    fn digit_count(mut n: u64) -> u8
                    {
                        // TODO
                    }

                    #[cfg(test)]
                    mod tests
                    {
                        // TODO
                    }
                
            

The expected result is as follows:

                
                    $ cargo run
                       Compiling digit_count v0.1.0 (pw_05_rust_basics/digit_count)
                        Finished dev [unoptimized + debuginfo] target(s) in 0.52s
                         Running `target/debug/digit_count`
                    digit_count(0) = 1
                    digit_count(1) = 1
                    digit_count(2) = 1
                    digit_count(4) = 1
                    digit_count(8) = 1
                    digit_count(16) = 2
                    digit_count(32) = 2
                    digit_count(64) = 2
                    digit_count(128) = 3
                    digit_count(256) = 3
                    digit_count(512) = 3
                    digit_count(1024) = 4
                    digit_count(2048) = 4
                    digit_count(4096) = 4
                    digit_count(8192) = 4
                    digit_count(16384) = 5
                    digit_count(32768) = 5
                    digit_count(65536) = 5
                    digit_count(131072) = 6
                    digit_count(262144) = 6
                    digit_count(524288) = 6
                    digit_count(1048576) = 7
                    digit_count(2097152) = 7
                    digit_count(4194304) = 7
                    digit_count(8388608) = 7
                    digit_count(16777216) = 8
                    digit_count(33554432) = 8
                    digit_count(67108864) = 8
                    digit_count(134217728) = 9
                    digit_count(268435456) = 9
                    digit_count(536870912) = 9
                    digit_count(1073741824) = 10
                    digit_count(2147483648) = 10
                    digit_count(4294967296) = 10
                    digit_count(8589934592) = 10
                    digit_count(17179869184) = 11
                    digit_count(34359738368) = 11
                    digit_count(68719476736) = 11
                    digit_count(137438953472) = 12
                    digit_count(274877906944) = 12
                    digit_count(549755813888) = 12
                    digit_count(1099511627776) = 13
                    digit_count(2199023255552) = 13
                    digit_count(4398046511104) = 13
                    digit_count(8796093022208) = 13
                    digit_count(17592186044416) = 14
                    digit_count(35184372088832) = 14
                    digit_count(70368744177664) = 14
                    digit_count(140737488355328) = 15
                    digit_count(281474976710656) = 15
                    digit_count(562949953421312) = 15
                    digit_count(1125899906842624) = 16
                    digit_count(2251799813685248) = 16
                    digit_count(4503599627370496) = 16
                    digit_count(9007199254740992) = 16
                    digit_count(18014398509481984) = 17
                    digit_count(36028797018963968) = 17
                    digit_count(72057594037927936) = 17
                    digit_count(144115188075855872) = 18
                    digit_count(288230376151711744) = 18
                    digit_count(576460752303423488) = 18
                    digit_count(1152921504606846976) = 19
                    digit_count(2305843009213693952) = 19
                    digit_count(4611686018427387904) = 19
                    digit_count(9223372036854775808) = 19
                
            

Sum of Divisors

You are going to write a program that adds up all the divisors of a positive integer (excluding the integer itself).

For this program, you will pass the positive integer to your program by using the command-line arguments. This part of the code will be given.

Also you will write a divisor_sum() function so that you can reuse it in another project. Therefore, you are going to write a library.

First, create the project.

                
                    $ cargo new divisor_sum --vcs none
                         Created binary (application) `divisor_sum` package
                    $ cd divisor_sum/
                
            

So far, we have used the term project, but as you can see, cargo uses the term package. So actually, the cargo new instruction creates a new package.

A package contains one or more crates.

A crate is either a binary crate or a library crate:

A package can contain:

A package contains a binary crate when the src/ directory contains a main.rs file. Therefore, the cargo new instruction generates a binary crate by default.

A package contains a library crate when the src/ directory contains a lib.rs file.

So far, we have created only one binary crate per package. For this exercise, we will create a binary crate and a library crate in the same package. We will implement the main() function in the binary crate and the divisor_sum() function in the library crate. To do so, we just have to create a new lib.rs file in the src/ directory. When there are a binary crate as well as a library crate, they both get their names from the package (i.e. divisor_sum).

Be careful! In this exercise, the divisor_sum name is used for different purposes.

Start by modifying the default main.rs file with the following contents:

main.rs
                
                    use divisor_sum::divisor_sum;

                    fn main()
                    {
                        let arg = std::env::args().nth(1);
                        let arg = match arg
                        {
                            Some(v) => v,
                            None =>
                            {
                                eprintln!("One parameter is required: an integer greater than 0.");
                                std::process::exit(1);
                            }
                        };

                        let n = match u64::from_str_radix(&arg, 10)
                        {
                            Ok(v) if v > 0 => v,
                            _ =>
                            {
                                eprintln!("The first parameter is not a valid number.");
                                std::process::exit(1);
                            }
                        };

                        println!("divisor_sum({}) = {}", n, divisor_sum(n));
                    }
                
            

For now, do not try to understand the main() function. You just have to know that:

Let us try to compile this code.

                
                    $ cargo check
                        Checking divisor_sum v0.1.0 (pw_05_rust_basics/divisor_sum)
                    error[E0432]: unresolved import `divisor_sum`
                     --> src/main.rs:1:5
                      |
                    1 | use divisor_sum::divisor_sum;
                      |     ^^^^^^^^^^^ use of undeclared type or module `divisor_sum`

                    error: aborting due to previous error

                    For more information about this error, try `rustc --explain E0432`.
                    error: Could not compile `divisor_sum`.

                    To learn more, run the command again with --verbose.
                
            

This code does not compile yet because we have not created the library crate. So let us do it.

To do so, you just have to create the following lib.rs file in the src/ directory.

lib.rs
                
                    pub fn divisor_sum(n: u64) -> u64
                    {
                        // TODO
                    }

                    #[cfg(test)]
                    mod tests
                    {
                        // TODO
                    }
                
            

Now, the divisor_sum library crate exists. But this code still does not compile because the divisor_sum() function is not implemented. As usual, you have to follow these steps:

  1. Implement divisor_sum() so that your code compiles.
    • As we actually want the test to fail, your divisor_sum() function is not meant to work properly and should not return the right values.
    • You just need your code to compile. To do so, you can use the unimplemented!() macro.
  2. Write the test for divisor_sum().
  3. To test the library crate only (and not the binary crate), you should use the following command: cargo test --lib. So, test the library and if the test does not fail, write it again.
  4. Write the divisor_sum() function and test it. The test should pass. If the test fails, write divisor_sum() again. Note that this function should not print anything on the terminal.

When you are done, you can execute your code via Cargo. To pass a parameter to your program, you must use the -- characters in the command line.

For example:

                
                    $ cargo run -- 50
                        Finished dev [unoptimized + debuginfo] target(s) in 0.01s
                         Running `target/debug/divisor_sum 50`
                    divisor_sum(50) = 43
                
            

Here are some examples of expected values. Cargo's messages have been snipped. Only the command lines and outputs of the program are shown:

                
                    $ cargo run
                    One parameter is required: an integer greater than 0.
                    $ cargo run -- 0
                    The first parameter is not a valid number.
                    $ cargo run -- abcd
                    The first parameter is not a valid number.
                    $ cargo run -- 1
                    divisor_sum(1) = 0
                    $ cargo run -- 3
                    divisor_sum(3) = 1
                    $ cargo run -- 6
                    divisor_sum(6) = 6
                    $ cargo run -- 9
                    divisor_sum(9) = 4
                    $ cargo run -- 11
                    divisor_sum(11) = 1
                    $ cargo run -- 28
                    divisor_sum(28) = 28
                    $ cargo run -- 30
                    divisor_sum(30) = 42
                    $ cargo run -- 496
                    divisor_sum(496) = 496
                    $ cargo run -- 8589869056
                    divisor_sum(8589869056) = 8589869056
                    $ cargo run -- 137438691328
                    divisor_sum(137438691328) = 137438691328
                
            

Now, we want to obtain an approximation of the elapsed time between invocation and termination of the program (the execution time). To do so, we first need a faster version of the program. It must be run in the release mode.

                
                    $ cargo run --release -- 137438691328
                       Compiling divisor_sum v0.1.0 (pw_05_rust_basics/divisor_sum)
                        Finished release [optimized] target(s) in 0.67s
                         Running `target/release/divisor_sum 137438691328`
                    divisor_sum(137438691328) = 137438691328
                
            

When we run the program via Cargo, it takes some time to check that all files have been compiled and some parts of your code will also be compiled if needed. So, to improve the precision of the execution time, it is advisable to run the executable file directly. This file can be found in the target/release/ directory.

                
                    $ target/release/divisor_sum 137438691328
                    divisor_sum(137438691328) = 137438691328
                
            

And now, let us get the execution time (the real time):

                
                    $ time target/release/divisor_sum 137438691328
                    divisor_sum(137438691328) = 137438691328

                    real    0m0.006s
                    user    0m0.004s
                    sys     0m0.000s
                
            

If your algorithm is efficient, the execution time is approximately equivalent to that of the C version.

Finally, clean your directory.

                
                    $ cargo clean
                    $ tree
                    .
                    ├── Cargo.lock
                    ├── Cargo.toml
                    └── src
                        ├── lib.rs
                        └── main.rs

                    1 directory, 4 files
                
            

Perfect Numbers

For this last exercise, you are going to write a program that lists all the perfect numbers from 1 to 100,000.

To do so, you need to use the divisor_sum() function you wrote in the previous exercise. Several methods are possible. Here, you will import this function from its library crate.

First, modify the default main.rs file with the following contents:

main.rs
                
                    use divisor_sum::divisor_sum;

                    fn main()
                    {
                        // TODO
                    }

                    fn is_perfect_number(n: u64) -> bool
                    {
                        // TODO
                    }

                    #[cfg(test)]
                    mod tests
                    {
                        // TODO
                    }
                
            

As said previously, the first line brings into scope the divisor_sum() function. But this is not enough, we also have to specify where the library of this function is located.

To do so, we need to modify the Cargo.toml file. This file can be used, among other things, to handle dependencies. So, append these two lines at the end of it.

Cargo.toml
                
                    # ...snip...

                    [dependencies]
                    divisor_sum = { path = "../divisor_sum" }
                
            

By using the same method as previously. complete the main.rs file and clean your directory when you are done.

Note that is_perfect_number():

The expected result is as follows:

                
                    $ cargo run
                       Compiling divisor_sum v0.1.0 (pw_05_rust_basics/divisor_sum)
                       Compiling perfect_numbers v0.1.0 (pw_05_rust_basics/perfect_numbers)
                        Finished dev [unoptimized + debuginfo] target(s) in 0.69s
                         Running `perfect_numbers/target/debug/perfect_numbers`
                    6
                    28
                    496
                    8128
                
            

Try to measure the performance of your code in the release mode and compare it with that of the C version. They should be similar.