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:
-
pw_05_rust_basics/
- AUTHORS
-
facto/
- Cargo.lock
- Cargo.toml
-
src/
- main.rs
-
fibo/
- Cargo.lock
- Cargo.toml
-
src/
- main.rs
-
isqrt/
- Cargo.lock
- Cargo.toml
-
src/
- main.rs
-
power_of_two/
- Cargo.lock
- Cargo.toml
-
src/
- main.rs
-
digit_count/
- Cargo.lock
- Cargo.toml
-
src/
- main.rs
-
divisor_sum/
- Cargo.lock
- Cargo.toml
-
src/
- lib.rs
- main.rs
-
perfect_numbers/
- Cargo.lock
- Cargo.toml
-
src/
- main.rs
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.
First Name
Family Name
Login
Email Address
The last character of your
AUTHORS
file must be a newline character.
For instance:
$ 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:
return
match
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-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!"
.
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:
-
The
Cargo.lock
file. You can ignore this file for the time being. -
The
target/
directory, which contains the binary and intermediate files generated by the compiler.
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.
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);
-
The left value is the returned value of
facto(0)
, which is 0. - The right value is the right value facto(0), which is 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:
- F0 = 0
- F1 = 1
- Fn = Fn-1 + Fn-2
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:
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.
-
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 ofcargo build
. Usingcargo check
is faster because it does not generate the final executable file.
-
As we actually want the test to fail,
your
-
Write the test for
fibo()
. In other words, completetest_fibo()
. Use onlyasser_eq!()
macros. -
Run the test.
It should fail because the
fibo()
function is wrong. If the test does not fail, write it again. -
Write the
fibo()
function and test it. The test should pass. If the test fails, writefibo()
again. Note that this function should not print anything on the terminal. -
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:
- r = n
-
While r2 > n:
- r = r + n/r
- r = r / 2
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.
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.
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.
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 binary crate generates an executable file.
- A library crate generates a library.
A package can contain:
- Any number of binary crates.
- Zero library crates or just one.
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.
- It is the name of the package.
- It is the name of the binary crate.
- It is the name of the library crate.
- It is the name of the function we want to write in the library crate.
Start by modifying the default main.rs
file
with the following contents:
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:
-
The
main()
function gets a parameter from the command line, calls thedivisor_sum()
function with this parameter and prints the result. -
The
use
keyword (line 1) brings into scope the name of thedivisor_sum()
function. The first divisor_sum is the name of the library crate. The second divisor_sum is the name of the function. In other words, it imports this function from the library crate so that we can use it in themain()
function (e.g. line 26).
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.
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:
-
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.
-
As we actually want the test to fail,
your
-
Write the test for
divisor_sum()
. -
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. -
Write the
divisor_sum()
function and test it. The test should pass. If the test fails, writedivisor_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:
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.
# ...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()
:
-
Should return
true
if its parameter is a perfect number. -
Should return
false
if its parameter is not a perfect number. - Should not print anything on the terminal.
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.