The Rust exam consists in the following:
- A lab project - up to 50 points (starting from lab 8)
- Lab activity - up to 7 points (one point for each of the first 7 labs)
- A course exam (week 15/16) - up to 30 points
The final grade for this exam is computed by applying Gauss curve formula on the final score (number of points) obtained by each student. The percentages proposed by ECTS ("top 10%, next 25%, next 30%, next 25%, lowest 10%") are used to transfer grades from one system to another and are based on the normalized distribution. The reality shows that the distribution of the scores obtained by the students following the evaluation does not always respect the normal Gauss distribution. For example, there are cases when 70-80% (or even more) of the scores received by the students are within the range [45-60]. Obviously, in these cases, one can not apply the same Gauss distribution as they will result in canceling the difference between someone with 80 points and someone with 60 points. For this reason, the ECTS percentages are only supported as a starting point in establishing the final grades; if the scores distribution is different than the normal one, the percentages will be adjusted accordingly.
Minimum requirements to pass this exam:
- 10 points for the course exam
- 20 points for the labs (activity + project)
Courses
-
Introduction
- Basic types
- Variables
- Operators
- Functions & Expression statements
- Basic statement (if, while, loop, ...)
- Link: Course 1
-
Ownership
- Prerequisite: String type
- Ownership management
- Borrowing & References
- Reborrowing
- Optimizations
- Link: Course 2
-
Error Management
- Enums
- Errors (panic, Option, Result)
- Statements (if let / while let / let ... else)
- Question mark operator
- Link: Course 3
-
Strings
- Type of strings, creation
- Methods
- Iterators
- Coercion
- Range operation, UTF-8 constraints
- String formatting
- Creating and updating strings
- Associated traits
- Link: Course 4
-
Complex data types
- Tuples
- Arrays
- Structures
- New type idiom
- Type alias
- Box (heap allocation)
- Link: Course 5
-
OOP concepts
- Methods for structs
- Traits
- Super Traits
- Special traits
- Link: Course 6
-
Generics and Match
- Generics
- Match
- Link: Course 7
-
Containers
- Closures
- Iterators
- Vectors
- Sorting data sequences
- HashMap
- HashSet
- BTreeMap
- BTreeSet
- Link: Course 8
Labs
Setting up the first Rust project
1. Prerequisites
On Windows: Install Visual Studio Community
- download from: link
- open installer
- make sure to select Desktop development with C++
On Unixes: Make sure to have a version of gcc or clang installed
- Ubuntu: sudo apt install build-essential
- Mac: brew install llvm (probably)
- BSD: pkg install lang/gcc
2. Download rustup
This is a tool that makes it easy to install the different components of the rust compiler
- on Windows, download the executable and run it
- on other OSes, run the command that begins with curl in a terminal
- type 1 to begin the install
3. Install Visual Studio Code (VSCode)
Go to the extensions page in the menu (a square made from 4 other squares, usually the last button) and install the following extensions
- rust-analyzer (id: rust-lang.rust-analyzer)
- C/C++ (id: ms-vscode.cpptools)
- Even Better TOML (id: tamasfe.even-better-toml)
- CodeLLDB (id: vadimcn.vscode-lldb)
Other optional extensions:
- Error lens (id: usernamehw.errorlens)

4. Create a new project
Cargo is a tool that helps building and packaging Rust applications. The usual workflow is:
- open a terminal
- cargo new hello_world => creates a new project with the name hello_world. This will also create a git repository for the project.
- cd hello_world => changes the directory in the newly created folder
- cargo build => builds the project
- cargo check => prints errors without actually building the application. Is used because it's faster than the build command.
- cargo run => builds and runs the project
- cargo fmt => formats the source code in the community approved code style
Right now, you should see Hello, world! on the terminal.
5. Listen to this at least four times
๐
6. Observe the files created by cargo
- Cargo.toml specifies meta information for our project like the name, the version, and the libraries that it uses
- .gitignore is a file that git checks in order to ignore files that are not needed to be committed.
- src/ is the folder that contains all the source files
- src/main.rs is the file that usually contains the main function ran at the start of the program
7. Open the project in VSCode
Type code . in the terminal
Or open a VSCode instance, go to File/Start=>Open folder=>navigate to the folder that contains Cargo.toml => OK

8. Run the program
Either by pressing the Run near the main, after the initial loading finishes.

Or:
- Go to menu and click the button with a triangle and a bug
- Click
create a launch.json file

- Select LLDB
- Press Yes
Now you can use F5/Ctrl+F5 and the other commands to run and debug the application.
9. Debug shortcuts
| Key | Description |
|---|---|
| F5 | debug the program/continue execution |
| Shift-F5 | stop execution |
| F9 | insert/delete breakpoint |
| F10 | next line |
| F11 | go into the function if there's any |
| Shift+F11 | run to the end of the function |
| F12 | go to the definition of the symbol |
| Alt+LeftArrow | go back |
| Alt+RightArrow | go forward |
| Ctrl+S | save file; this will usually trigger the error checking |
Print example
#![allow(unused)] fn main() { let rabbits = 5; println!("Hello world! I have {} rabbits.", rabbits); }
Problems
At the end, upload all the problems below to a GitHub repository under a folder named lab01.
Make sure to format (cargo fmt) your code before uploading.
-
Make a function that calculates if a number is prime, and call it with every number from 0 to 100, printing the primes.
-
Make a function that calculates if two numbers are coprime, and call it with pairs of every number between 0 and 100.
-
"Sing" the 99 bottles of beer problem.
Use the debugger after this: put a breakpoint on the first line of main, and run it line by line. Try to get familiar with the shortcuts. Observe the variables in the panel as they change.
String examples
Creation:
fn main() {
let s: String = String::from("a string");
println!("s = {s}");
}
Length:
fn main() {
let s: String = String::from("a string");
println!("len = {}", s.len());
}
Concatenation:
fn main() {
let mut s: String = String::from("123");
s += "456";
s.push_str("789");
s.push('0');
println!("{s}");
}
Subslice:
fn main() {
let s: String = String::from("ABCDEFG");
println!("{}", &s[1..3]);
}
str type:
fn main() {
let s: String = String::from("ABCDEFG");
let slice: &str = &s[1..3];
println!("{}", slice);
}
Problems
At the end, upload all the problems below to a GitHub repository under a folder named lab02.
P1
Create a function named add_chars_n which will take tree arguments (the string, the char, and the number), and returns a string with n characters added to the end of the string. The following main function to compile:
fn main() {
let mut s = String::from("");
let mut i = 0;
while i < 26 {
let c = (i as u8 + b'a') as char;
s = add_chars_n(s, c, 26 - i);
i += 1;
}
print!("{}", s);
}
P2
Rewrite the previous code so that the function takes a reference to a string and doesn't return anything.
P3
Write the following functions; they all take a string as first argument in whichever form you like:
- add_space: concatenates n spaces to the string
- add_str: concatenates a str to the string
- add_integer: concatenates an integer to the string. Add separators at every 3 digits.
- add_float: concatenates a float to the string
You must do the conversion from the numbers to the string by hand, without any help from std.
Call all the above functions as many times as you want in order to create the following string exactly. Do not use add_str with any constant spaces. Print the result to the terminal.
I ๐
RUST.
Most crate 306_437_968 and lastest is
downloaded has downloads the version 2.038.
Recap
Enums can be used as classic C enums:
#![allow(unused)] fn main() { #[derive(PartialEq)] enum Color { Red = 2, Green = 10, Blue, } }
Or as sum types that can contain different types inside them:
#![allow(unused)] fn main() { enum Values { Integer(i32), Float(f32), Character(char), Null } }
These can checked in the following ways:
#![allow(unused)] fn main() { let x = Color::Red; if x == Color::Blue { // ... } let v = Values::Integer(5); if let Values::Integer(x) = v { // do something with an i32 } match v { Values::Integer(x) => {}, // do something with an i32 Values::Float(y) => {}, // do something with an f32, _ => {}, // for all the other variants } }
The Option type can be used to represent a value that might be missing, and the Result type represents a value that is either an ok value, or an error. Both of these can be propagated using the ? operator.
#![allow(unused)] fn main() { fn f1() -> Option<u32> { Some(5) } fn f2() -> Option<u32> { Some(10) } fn g() -> Option<u32> { let x = f1()?; let y = f2()?; let sum = x + y; Some(sum) } }
A typical usage of Result and error propagation might look something like:
#![allow(unused)] fn main() { enum MyError { DivByZero, } fn f(x: u32, y: u32) -> Result<u32, MyError> { if y == 0 { Err(MyError::DivByZero) } else { Ok(x / y) } } }
Note: for functions that can fail but wouldn't return anything if it succeeds, the correct return type is Result<(), Error>.
Panicking can also be used as a way to handle an error that can't be processed:
#![allow(unused)] fn main() { let r1 = 20; let r2 = 10; if r1 > r2 { panic!("Expecting r1={r1} to be smaller than r2={r2}"); } }
Problems
For each problem, have code that checks both the success and fail cases.
-
Make a function that returns the next prime number following the number took as an argument, which has the
u16type. If no such number exists (the next prime number will not fit in anu16), the function will returnNone. Example signature:#![allow(unused)] fn main() { fn next_prime(x: u16) -> Option<u16> }Call this function repeatedly until you get a
None, printing each prime. -
Implement two functions for making checked addition and multiplication for the
u32type. These functions can fail if the resulted value doesn't fit in anu32. If a failure occurs, usepanicto end the application. -
For the previous problem, instead of panicking, use the
Resultmechanism to return and propagate errors. Make an error type that represents this error case.Make another function that tries to use the above functions and propagate any errors that appear.
-
Make an error type that can represent the following errors:
- character is not ascii
- character is not a digit
- character is not a base16 digit
- character is not a letter
- character is not printable
Create the following functions that take a
charas argument (each function can fail and will return the relevant error):- to_uppercase: fails if the input is not a letter
- to_lowercase: fails if the input is not a letter
- print_char: fails if the input is not printable
- char_to_number: fails if the input is not ascii, or if it's not a digit
- char_to_number_hex: fails if the input is not ascii, or if it's not a base16 digit
- print_error: receives an error as input, and prints a human readable message for each error type
-
Make an application of your choice that uses
Option,Result, or both.
Bonus
While writing error types manually gives the most flexibility to the programmer, it is often tedious, and most error types are very simple. Because of this, there are ways to automate this using external crates.
A crate is Rust's idea of a package. Every project is made of one or more crates that put together make an executable or a library. cargo is the build tool and package manager that understand how crates work, which crate depends on which crate, and how to build the final artifact.
The primary way to find a crate that might help you is to look on crates.io. This is also the main source that is considered by cargo when it's trying to build the project.
For this lab, we will be using the thiserror crate to make it easier to write errors. The first step to make this happen is to go in the Cargo.toml file in your project:

TOML is a simple configuration format that is more powerful than an INI, and designed to be easily edited by hand by humans.
The [dependencies] tag say on what crates the current crate depends on. To make use of our new crate, we have to specify its name and the version we want to use. It is not necessary to spell out the exact version; for instance, if we say 1.0, cargo will understand that it can choose any version that starts with that. At the time of this writing, it will choose 1.0.37 because that is the latest one and there's no other crate that has more restrictions on the version.
[dependencies]
thiserror = "1.0"
Remember to save the file! Now, when we execute the next cargo command, or when VSCode in background executes cargo, it will fetch the crate from GitHub and use it.
A typical usage looks like this:
use thiserror::Error; #[derive(Error, Debug)] enum MyError { #[error("{0} divided by zero")] // {0} will take the first value of the variant DivByZero(u32), } fn f(x: u32, y: u32) -> Result<u32, MyError> { if y == 0 { Err(MyError::DivByZero(x)) } else { Ok(x / y) } } fn main() { match f(5, 2) { Ok(x) => println!("5/2={}", x), Err(e) => println!("err: {}", e), }; match f(5, 0) { Ok(x) => println!("5/0={}", x), Err(e) => println!("err: {}", e), }; }
As an optional exercise, go back and use this crate for the problems where you made an error type by hand.
Recap
Characters are stored in a string using the UTF8 encoding. That means that a single character (codepoint) can take several bytes. For instance, the character ฤ is written as 2 bytes: [196, 131]. The character ๐ถ is written as 4 bytes: [240, 159, 142, 182].
To iterate over the characters of a string, use .chars():
let s = "abc";
for i in s.chars() {
println!("{}", i);
}
Reading and writing text files example
use std::{io, fs};
fn do_stuff() -> Result<(), io::Error> {
let s = fs::read_to_string("src/main.rs")?;
fs::write("copy.rs", &s)?;
Ok(())
}
Problems
For each problem, make sure to test several success and failure cases.
Folder name: lab04
P1
Read a text file. Calculate and print its longest line considering the number of bytes, and the longest line considering the number of actual characters.
Example input:
strings are fun
๐๐ถ๐๐๐๐๐โ๐
rust
supercalifragilisticexpialidocious
P2
Implement the ROT13 cipher. This cipher will rotate each ASCII letter by 13. Be mindful that lowercase letter transform into lowercase letters, and uppercase letters into uppercase letters.
If any non-ASCII character is encountered, print an error message and stop. Read and write the text however you want.
P3
Have a list of abbreviations (this can be hardcoded):
pentru pt
pentru ptr
domnul dl
doamna dna
Read a phrase from a file and replace any word that is an abbreviation into its correct form. Assume that the phrase only contain spaces for delimitations.
Example:
Am fost la dl Matei pt cฤ m-a invitat cu o zi รฎnainte
=>
Am fost la domnul Matei pentru cฤ m-a invitat cu o zi รฎnainte
P4
Read the hosts file from your system (Windows: C:\Windows\System32\drivers\etc\hosts, Unix: /etc/hosts). Split the lines, ignore the lines that start with #, and print the content in the format below. Take into considerations only the first two text columns of each entry.
localhost => 127.0.0.1
...
Note: the file might be fully commented if you're running a security product. Try with a file written in another location if that is the case.
Bonus
Generate a 4GiB file with ascii text by repeating some text as many times as necessary.
Go back to P2 and use the app from there to encrypt the 4GiB file. Note down how much time it takes. Now optimize the code as much as you can so the time gets as small as possible.
Timing code example:
use std::time::Instant;
fn do_stuff() {
for _ in 0..1_000_000 {
print!("");
}
}
fn main() {
let start = Instant::now();
do_stuff();
println!("{:?}", start.elapsed());
}
Recap
Structs declaration and initialization:
struct Point {
x: i32,
y: i32
}
let p = Point { x: 5, y: 6 };
Arrays:
let array = [1, 2, 3];
let array: [u32; 3] = [1, 2, 3];
let array: [u32; 3] = [0; 3];
let matrix = [[' '; 10]; 10]; // 10x10 char array initialized with spaces
Problems
P1
A student has the following properties: name, phone and age. Read a file with the following format:
Constantin,0711111111,21
Mihai,0722222222,23
Elena,073333333333,25
Diana,0744444444,20
Find and print the oldest and the youngest student.
P2
Create a canvas structure that can hold (55 lines)x(100 characters each) that can be "painted" on, and the corresponsing functions, so this code compiles and runs properly. The canvas will be initialized with spaces, and have the possibility to set pixels knowing the coordinates and the values in the (x, y, ascii value) format, and print the canvas at the end.
Info
Serializing is the process of writing in-memory objects (structs, enums, simple types) into a format that can be deserialized later in order to yield the same object the process started with. The format is usually agnostic when it comes to processor architecture.
Serde is a crate used for serializing and deserializing Rust types in a plethora of formats, the most used being JSON.
To use it, go to Cargo.toml and add the following under [dependencies]:
serde = "1"
serde_derive = "1"
serde_json = "1"
Serde is made out of several crates, each with its own purpose.
serdeis the main crate that defines the interfaces and base types that every other crate usesserde_deriveis the crate that implement the macro that maps Rust's native types to something more genericserde_jsonis the crate that implements the JSON format, using the other two
This crates use Rust's powerful type system to transform JSONs from and to strongly typed structs, enums, arrays, maps, etc. Usually, JSON objects map to a struct, JSON arrays map to an array or a vector, bools to bools, numbers to integers or floats, strings to strings.
Additionally, Options are useful because they say that a field can be missing without erroring out.
Example:
use std::fs;
use serde_derive::Deserialize;
#[derive(Debug, Deserialize)]
struct Person {
name: String,
known_for: Option<String>,
age: u32,
alive: bool,
}
fn main() {
let content = fs::read_to_string("person.json").unwrap();
let p: Person = serde_json::from_str(&content).unwrap();
println!("{:?}", p);
}
{
"name": "Bill Joy",
"known_for": "โ๏ธ",
"age": 67,
"alive": true
}
Mapping:

P3
Go back to P1 and solve it using JSONS. Each line now contains a JSON that describes the student, like so:
{ "name": "Constantin", "phone": "0711111111", "age": 21 }
Bonus
Implement life.
More info
Have a matrix of constant values of elements (bool, u8, chars) that are read from a file, each element representing a cell that is either dead or alive. Each cell has 8 neighbors, and the number of neighbors it has contributes to whenever if it survives or not, with the following rules:- Any live cell with two or three live neighbours survives.
- Any dead cell with three live neighbours becomes a live cell.
- All other live cells die in the next generation. Similarly, all other dead cells stay dead.
Use blinker (period 2) and pulsar (period 3) to test it. Simulate at least 3 transitions.
Hint: you can use a type alias for the matrix.
Info
Defining and implementing a trait:
trait MyTrait {
fn print_static();
fn print_member(&self);
fn set(&mut self, x: u32);
fn consume(self);
}
struct S {
x: u32,
}
impl MyTrait for S {
fn print_static() {
println!("wow! ๐");
}
fn print_member(&self) {
println!("{}", self.x);
}
fn set(&mut self, x: u32) {
self.x = x;
}
fn consume(self) {
println!("and we're done");
}
}
fn main() {
S::print_static();
// static fn
S { x: 5 }.print_member();
// const self ref
let mut x = S { x: 10 };
x.set(345);
x.print_member();
// mutable self ref
x.consume();
// self
// x.print_member();
// variable was moved
}
Vectors:
fn main() {
let mut v = Vec::new();
v.push(("abc", 5));
v.push(("def", 6));
// iterate immutably
for (s, n) in &v {
println!("{} {}", s, n);
}
// iterate mutably
for (_, n) in &mut v {
*n += 1;
}
// iterate and destroys the vec
for (s, n) in v {
println!("{} {}", s, n);
}
}
Problems
P1
For this problem you are supposed to write an application that emulates a terminal that implements several commands. To emulate a command, make a trait with the following functions:
- get_name: returns a string with the name of the command
- exec: takes a slice of strings with the command arguments
Make a struct for each command and implement the above trait for the following commands:
- ping: prints "pong!"
- count: prints the number of arguments it received;
count a b c-> will printcounted 3 args - times: prints how many times this command (only this command!) has been called
- a new command of your choosing
Add a special stop command (this doesn't have to be implemented through the trait) that stops the execution. Suggest the correct command if the uses misspells it (eg: if it says TIMES).
Example input: file.
The application will print the error if anything bad happens. The application must not panic.
Make a struct that keeps the commands in a collection, reads the commands from a text file, and executes them. This struct needs to have the following functions:
- new: creates a new instance
- register: adds the new command to the collection
- run: reads lines a file, splits them based on spaces, uses the first word as the command and searches it in the collection, and executes the command with the remaining arguments. This function should treat the case where the command is not valid or if the line is empty. It will continue to run even on error cases.
Example main:
fn main() {
let mut terminal = Terminal::new();
terminal.register(Box::new(PingCommand {}));
terminal.register(Box::new(CountCommand {}));
terminal.register(Box::new(TimesCommand { count: 0 }));
terminal.run();
}
Bonus
SQLite is the most used database engine on the planet, being used in desktop, laptops, phones, aircrafts, TVs, browsers, antiviruses, etc. The difference between SQLite and other databases is that SQLite is designed to not have the need of a server, and is routinely used by applications to store data (eg: your phone stores the contact list in a SQLite database).
rusqlite is the crate that is used the most to interact with SQLite databases. Add the following under dependencies:
rusqlite = { version = "0.30.0", features = ["bundled"] }
Opening and creating tables if they not exists (because we'll probably run this multiple times):
let conn = Connection::open("persons.db")?;
let create = r"
create table if not exists persons (
name text not null,
age integer not null
);
";
conn.execute(create, ())?;
Note: you need execute_batch if you want to create more than a table.
Inserting data in a table:
let name = "sergiu";
let age = 22;
conn.execute("insert into persons (name, age) values (?1, ?2);", (name, age))?;
Notice that the SQL query inserts the values ?1 and ?2. This gets replaced by the arguments that follow.
Selecting data from the database:
struct Person {
name: String,
age: u8
}
let mut stmt = conn.prepare("select * from persons")?;
let person_iter = stmt.query_map([], |row| {
Ok(Person {
name: row.get("name")?,
age: row.get("age")?
})
})?;
for i in person_iter {
let i = i?;
println!("name={}, age={}", i.name, i.age);
}
The query_map function accepts as first argument the possible added arguments with ?1, ?2, etc., just like execute does. In this case, this happens to be empty.
P2
Add a bookmark "bk" command that uses a SQLite database to store bookmarks. Each bookmark can have a name and an url.
Based on the first argument, the command will do the following:
add <name> <url>: adds an url to the databasesearch <name>: prints all the names and the urls that containsnamein their name
Usage example:
bk add foxes https://www.reddit.com/r/foxes/
bk add rust_book https://doc.rust-lang.org/book/
bk add foxes_pics https://www.boredpanda.com/beautiful-fox-pictures/
bk search fox
bk search rust
Info
Implementing an operator:
use std::ops::Add;
struct S(u32);
impl Add<S> for S {
type Output = S;
fn add(self, rhs: S) -> Self::Output {
S(self.0 + rhs.0)
}
}
Taking a generic:
use std::{fmt::Debug, ops::Add};
fn f<T: Debug + Add<Output = u32>>(x: T, y: T) {
println!("{}", x + y);
}
Assert:
fn main() {
assert!(true);
assert_eq!(5, 5);
assert_ne!(10, 11);
}
Problem
Define and implement code for a datatype Complex that represents a complex number. The type will keep its data as f64. The type has to implement the following:
- associated function
newthat takes 2 arguments, each with a generic type, that will be used to convert and initialize the type; - associated function
conjugatethat returns the conjugate of the number; - implement From for
i32andf64for the type; the number will be the real part, and the imaginary part will be 0; - implement Add, Sub, Mul that is generic over any type that can be converted to
Complex; - implement Neg;
- implement Display that respects the rules shown below;
The following code must compile and print ok! when run. Do not modify it.
fn eq_rel(x: f64, y: f64) -> bool {
(x - y).abs() < 0.001
}
// This is a macro that panics if 2 floats are not equal using an epsilon.
// You are not required to understand it yet, just to use it.
macro_rules! assert_eq_rel {
($x:expr, $y: expr) => {
let x = $x as f64;
let y = $y as f64;
let r = eq_rel(x, y);
assert!(r, "{} != {}", x, y);
};
}
fn main() {
let a = Complex::new(1.0, 2.0);
assert_eq_rel!(a.real, 1);
assert_eq_rel!(a.imag, 2);
let b = Complex::new(2.0, 3);
let c = a + b;
assert_eq_rel!(c.real, 3);
assert_eq_rel!(c.imag, 5);
let d = c - a;
assert_eq!(b, d);
let e = (a * d).conjugate();
assert_eq_rel!(e.imag, -7);
let f = (a + b - d) * c;
assert_eq!(f, Complex::new(-7, 11));
// Note: .to_string() uses Display to format the type
assert_eq!(Complex::new(1, 2).to_string(), "1+2i");
assert_eq!(Complex::new(1, -2).to_string(), "1-2i");
assert_eq!(Complex::new(0, 5).to_string(), "5i");
assert_eq!(Complex::new(7, 0).to_string(), "7");
assert_eq!(Complex::new(0, 0).to_string(), "0");
let h = Complex::new(-4, -5);
let i = h - (h + 5) * 2.0;
assert_eq_rel!(i.real, -6);
let j = -i + i;
assert_eq_rel!(j.real, 0);
assert_eq_rel!(j.imag, 0);
println!("ok!");
}
Math help: link.
Bonus
Also implement AddAssign, SubAssign, MulAssign, and any other operator or special trait that makes sense for the type.
Info
The applications must:
- use the 2024 edition on a stable compiler
- compile without compiler warnings (
cargo check) - compile without clippy warnings (
cargo clippy) - resolve warnings properly, and not just hide them (eg. no underscore to hide an unused variable)
- propagate errors to main correctly (where it makes sense)
- not use
unsafeunless otherwise specified - pass a run through miri (
cargo miri run) if the project is usingunsafe - respond to command line arguments that changes its behavior including a help command that prints the available commands
The project will be put in the project folder in the repository used for laboratories.
Grading
C projects
- 30 Points
- Full Functionality
- Git Support
- Presented until week 11
- 25 points
- Full Functionality
- Git Support
- Presented in week 12
B projects
- 40 Points
- Full Functionality
- Git Support
- Git Features Updates
- Presented until week 12
- 35 points
- Full Functionality
- Git Support
- Git Features Updates
- Presented in week 13
A projects
- 50 points
- Full Functionality
- Git Support
- Git Features Updates
- Presented until week 13
- 45 points
- Full Functionality
- Git Support
- Git Features Updates
- Presented until week 14
Glossary
Full Functionality = Means that every requirement described for the project has to be addressed in its implementation
Git Support = The project has to be uploaded to a GIT account (github) that is shared with the teaching assistant
Git Features Updates = If a project has multiple features (e.g. for the Total Commander Browser this will be the ability to copy file, the ability to delete files, etc), every feature has to be committed to Git in a separate commit (with a clear name) so that we can track the evolution of the project.
Final observations
Announce your teaching assistant that you are ready to present a project with 1-2 days before the presentation.
We will only grade if the previous described conditions are achieved (e.g. if you chose a project of type A you can only receive either 50 or 45 points - a partial project will NOT BE GRADED). Please keep this in mind when you are choosing the project category.
We encourage you to try to present before the final moment when your type of project can be presented. If during a presentation, we find problems with a project and there is still time until the last moment your project can be presented, you will be able to re-present again (as long as we respect the final week when that project can be presented).
Before the presentation we will ask you to identify yourself (using an identity act).
The grades that you received are not FINAL. In Week 14 - 15 we will check for plagiarism among the submitted project. If this case, the final grade for the lab exam will be adjusted accordingly.
Choosing the project
You will be registering your preferences for the project using the following link using a Google Form that will be posted on the Discord server. On this form, in addition to your Name/Surname and Faculty Number, you must indicate your preferences for ALL PROJECTS from the project list for types A, B, and C. The format is simple: <Project ID>, <Project ID>, .... For example: 10,1,5,6,7,... for category A means that you would most like to receive project 10, then if not possible, project 1, then if not possible, project 5, and so on.
If you provide an incomplete list or do not complete a list at all (for example, someone who does not want type A projects), a series of preferences will be automatically generated to assist the system.
For instance, if no one wants problem 7 and someone does not complete their preferences (either partially or not at all), problem 7 will be added to the list. In the case of partially completed preferences, it will be added to the end of the preferences list. Our recommendation is that those who do not want a project of a certain type should NOT COMPLETE THE PREFERENCES LIST. This will help the algorithm assign projects that some people desire much more easily.
Other things to note - please adhere to the format --> only integers separated by commas will be accepted. In case of an invalid format, we will attempt to correct where possible. However, if correction is not possible, the field will be considered EMPTY, and values will be assigned to assist the algorithm. Another observation is that if you enter incorrect values (for example, preferring problem 105 which does not exist), these will be ignored in order (so be careful how you fill them out).
VERY IMPORTANT --> Those who do not register on this form WILL NOT RECEIVE PROBLEMS and therefore cannot pass the Rust course. Everyone should register (even if they have no preferences)!
After this, you will receive 3 projects each (one from category A, one from category B, and one from category C). In the following week, please inform your assistant which of the assigned problems you want to work on (notification can be made in the lab or on Discord).
You are NOT allowed to exchange the assigned problem with another colleague (the assistant will only monitor what the algorithm has assigned to you). Also, once you have opted for a problem, you cannot change it (for example, suppose the algorithm assigns you: problem 3 (category A), problem 7 (category B), and problem 2 (category C)). If you tell the assistant that you are working on problem 3 (category A), then you cannot later say that you no longer want to work on that problem and opt for problem 7 (category B).
1. my_svn
Develop a revision control application. The application must implement the following equivalent functionalities from git:
- being able to maintain multiple branches;
- checkout between branches;
- commits separate on each branches;
- simple merges;
- diff between branches and with the previous commit;
- git status;
- .gitignore functionality;
2. bit_serde
Write a serialization and deserialization library that will encode values in the smallest possible number of bits. The library will provide a macro similar to #[serde(Serialize, Deserialize)] that will generate the ser/de code. You are not allowed to use the serde crate or any other similar crate for this.
The library will serialize values at the bit level. For instance, a 2 bit value followed by a 3 bit value will be put in the same byte, not in different ones. A u32 value that follows will start whenever the last one left off in the bit stream.
Example code:
#[bit_serde(Serialize, Deserialize)]
struct Player {
name: String,
#[max = 100]
level: u8,
class: Class,
race: Race,
guild: String,
}
#[bit_serde(Serialize, Deserialize)]
enum Class {
Warrior,
Assassin,
Archer,
Wizard,
Priest,
Evocator,
}
#[bit_serde(Serialize, Deserialize)]
enum Race {
Human,
Beast,
Fairy,
}
The String will be serialized as a variable integer (any way) + the utf8 bytes.
The level field will be serialized as 7 bits (log2(100) = 6.64). The Class enum only needs 3 bits (log2(6) = 2.58), while Race only needs 2 (log2(3) = 1.58). A bool would only need one bit.
The lib must support all std number types (i8..=i128, u8..=128, bool, char, f32, f64), String, Vec<T> where T: Serialize/Deserialize.
The lib will return an error if the fields annotated with #[max] have values that are out of bounds. Alternatively, the lib can provide a type for unusual sized numbers that's implemented using const generics.
3. vfs
Create a library that implements a virtual filesystem. All the data about the virtual files will be kept in a single big file on the real filesystem. The library must support directories and regular files. It must also keep track of the files metadata (size, creation time, last write time). The virtual filesystem must not get corrupted if the application unexpectedly stops.
Usage of SQLite or other libs is not allowed. The management of the real file must be done by hand.
The library must have exhaustive tests to show it's correct. The library must be usable with more than one file at a time. The library must be able to work with files/directories that are too big to be kept in memory all at once. It is ok to have the library only usable from a thread.
Example code:
let vfs = Vfs::open("realfile.vfs");
vfs.create_dir("rs")?;
{
let mut f1 = vfs.create("rs/abc.txt");
let mut f2 = vfs.create("rs/def.txt");
f1.write_all(b"hello")?;
f2.write_all(b"world")?;
}
let mut data = String::new();
for entry in vfs.read_dir("rs")? {
let entry = entry?;
data.clear()
let mut file = vfs.open(entry)?;
file.read_to_string(&mut data)?;
print!("{}", data);
}
println!();
4. offline_messenger
Develop a client/server application that allows the exchange of messages between connected users and provides the functionality to send messages to offline users, with the latter receiving messages when they connect to the server. Additionally, users will have the ability to send a specific reply to certain received messages. The application will also provide conversation history for each user individually. The communication must be encrypted, with different keys for each connection.
5. mclient
Make a CLI chat client for a Minecraft server. The client must be able to:
- do a status request and print the server status and save the server image to a file
- connect to a server
- read the chat in real time and write to it
- survive the connection indefinitely
- correctly display colored text and all the formatting that a regular supports
- print the online players
Tip: Use a version earlier than 1.19.
6. taskmanager (windows: unsafe)
Make an application that has a GUI and shows information about the processes currently running on the system. Needed information for each process:
- process name
- CPU used
- memory used
- path file
- username of the user
Global information:
- total cpu used
- total memory used
There will be two ways to view processes: as a tree, or as list.
The tool must work on at least one of the mainstream OSes (Linux, Windows, Mac).
7. advanced_rsync
At least two locations are received that need to be kept synchronized (more locations of the same type can be received at the same time). A location has the following format: <LOCATION_TYPE>:<Path_in_location>. Examples:
ftp:user:password@URL/a.b.c
zip:C:/abc/d.zip
folder:C:/aaa
The app runs continuously and keeps the two locations synchronized. Specifically:
- If a file is created in one location, the file is duplicated in the other location.
- If a file is deleted from one location, it is also deleted from the other location.
- If a file is modified in one location, the modification is copied to the other location.
Upon initial startup, synchronization is performed as follows:
- If a file exists only in one location, it is copied to the other location.
- If the same file exists in both locations but there are differences, the newest version is copied.
zip archives can be treated as read only, only ftp and folders can change.
8. trap_the_mouse
Implement the Trap The Mouse game. The game will be split in two:
- a server which hosts the game. The user can create rooms where they wait for other players. When another player joins, the game starts for both players.
- a client which has a GUI where the user can connect to a room and play the game.
The client also has the possibility to play with the computer. In this case, it still has to connect to the server, and the server is the one that dictates the moves of the mouse.
Resources: wikipedia.
9. k9
Make a discord bot that implements a few commands:
- quote: will send a random Doctor Who quote
- doctor
: will send a picture with the n-th doctor - episode
: will search all the titles of all episodes for that text, and print the information about that episode (title, runtime, season/episode number) - points: prints a leaderboard with how many points every user has.
The bot will also post random questions about the show at regular intervals (only if the last one was correctly guess). The first user that replies with the correct answer gets a point.
10. tpaste
Make a tool that can be able to upload terminal output to a web service and get a web link that can be shared at the end.
Example usage:
> cat /proc/meminfo | tpaste
https://tpaste.fii/E2WtkyE9LZ
Components:
- client: the tpaste command will receive the input and make a web request to a service that uploads the received text, and prints the link that it got
- server: the web service that receives the requests, and resolve them. Additionally, the server hosts a web server so if you to the provided link, it will show the published result. There will also be a page for every user with links to all the pastes they uploaded along with timestamps.
The server also must enforce authentification. The client must have a command where a user can register with a new account and one where the user can login with an existing account. The server will give the client a token that will be kept in a file on the system, so authentication is only done one time. The token will expire after 60 days. The client will send the token at every request and the server will validate it.
11. chess
Implement a GUI Chess application where players interact by dragging and dropping pieces. All the following features must be implemented:
- A visible sidebar displays the chronological history of all moves made
- When selecting a piece, all available moves must be highlighted
- It enforces all standard rules including castling, en passant, and checkmate detection
- Each player has a total bank of 5 minutes for the entire match -> depleting this time results in an automatic loss
- Prevents illegal moves and displays the state of the game ("White won", "Checkmate" etc.)
12. TODO list
Develop a GUI Rust application that allows users to manage tasks with clarity and time awareness. Each task should include:
- title
- notes
- independent countdown timer
- deadline & color-coded deadlines (e.g., red for overdue, yellow for due soon, etc.)
- method that will prioritize a task
13. weather dashboard
Develop a GUI weather dashboard that allows users to fetch real-time weather from an API and display forecasts. The dashboard should have the following features:
- display the current weather for a given location
- display the forecast for the next 7 days
- display the air pollution index for a given location
- ability to add/remove a new location to the favorites list
- a favorites list of locations where the user can quickly access the weather for those locations
14. anime timeline explorer
Develop a GUI anime timeline explorer that allows users to display the chronological order of episodes or arcs for popular series. The explorer should have the following features:
- display the chronological order of episodes or arcs for a given series
- display the title, description, and thumbnail for each episode or arc
- a favorites list of series where the user can quickly access the timeline for those series (also adding/removing series from the favorites list)
- search filters like title, genre etc. Resources: Jikan API (or another API)
15. tetris
Develop a GUI Tetris game. The following features must be implemented:
- support keyboard navigation for piece rotation and movement
- implement score multipliers for clearing multiple lines simultaneously
- include a game loop with pause/resume functionality
- include side panels displaying the next upcoming piece and current score
1. rterm (windows: unsafe)
Create a terminal similar to sh/bash that implements the following commands:
- copy/move/delete files and directories
- list/create/modify/delete registry keys (only on Windows)
- list/kill running processes
The terminal will try to immitate a Unix terminal as far as possible, including the name of the commands.
Tip: Use a VM to test registry modification functionalities.
2. calculator
Write an app that will receive a mathematical expression from the command line and print the calculation process step by step.
The calculator will have the following operations: addition, subtraction, multiplication, division, exponentiation, square root, logarithm, trigonometric functions, paranthesis.
Have different steps for lexing, parsing, and resolving.
Example input: 2 * (3 + 5 / 4) - (5 ^^ 2 + 8) / 11
Example output:
= 2 * (3 + 1.25) - (5 ^^ 2 + 8) / 11
= 2 * 4.25 - (5 ^^ 2 + 8) / 11
= 8.5 - (5 ^^ 2 + 8) / 11
= 8.5 - (25 + 8) / 11
= 8.5 - 33 / 11
= 8.5 - 3
= 5.5
3. splitter
Make an application can receive big files and split them in several smaller files. This is useful for sending a big file over a channel that only allows a small chunk of data to be sent at a time. The application must be also be able to put the original file together given all the small files.
The application must be also be able to receive the max size for a chunk at cmdline. Known size suffixes: b, kb, mb, gb. No suffix means bytes
Example:
./splitter split a.zip -s 1M
# Will result in the tool writing a.zip.part0001.split, a.zip.part0002.split, etc. all with size of 1mb except the last one
./splitter unsplit a.zip
# The app will seach all the a.zip.part*.split files and put them togheter.
The application should be able to tell if not all the files are present, and if any was corrupted.
Useful crates: ureq for downloading over http, serde_json for json, chrono.
4. connect four
Implement a TUI/GUI application featuring the Connect Four game. The game must support both local two-player matches and single-player modes against the computer and automatically highlight the connecting four discs visually when a winning condition (horizontal, vertical, or diagonal) is met.
5. recursive_grep
Create a tool that searches for a substring in every file in a directory and every subdirectory, and print the name of the file and the relevant line with its line number.
Command line options:
- string to search for
- max number of lines (after which the application exits) (default: infinite)
- ignore case (default: no)
- only count (print only the number of matches per file, without the lines) (default: no)
- option to enable regex searching.
6. tar
Implement a tool that can work with files in the tar format. The tool must be able to pack all files in a directory recursively, and unpack all the files in a tarball. The tool must also support gzipped files (.tar.gz) for both operations.
Example commands:
./tar pack path/ -c
./tar unpack x.tar
./tar unpack x.tar.gz
7. wiki_stats
Download the dataset from here.
Make a tool that reads the zip archive provided, and extract the below information from all the data in all the jsons. The output will be written to a file.
- a frequency list of all the words as written
- a frequency list of all the words as lowercase
- the title, the json path in the zip, and the size of the longest article
- the title, the json path in the zip, and the size of the longest title
8. crates_stats
Clone the crates.io index repo.
Read the information about all the crates. Take into consideration only the most recent version of a crate. Print to a file the following information:
- the crate with the most dependencies (and the dependencies)
- the crate with the most dependants (and the dependants)
- the crate with the most features (and the features)
- the crate with the most versions
9. two_thousand_and_forty_eight
Implement the 2048 game. The game must either use a GUI interface, or a TUI one. If the user closes the game, the game must save the state, and restore it at the next start, unless the game was finished.
10. my_ssh
Implement a client/server pair capable of encrypted authentication and communication. The server will execute commands from the client and return their output to the client. The commands are executable from the path with any number of arguments; cd and pwd will function normally. Multiple commands can be executed sequentially or redirected using: |, <, >, &&, ||, ; with the same semantics as bash.
11. snake
Develop a GUI/TUI game where players control a snake that increases in movement speed as it consumes food and grows. The game map should incluse obstacles in addition to standard border walls. It persists high scores to a local file to display alongside the current session's score and includes a pause menu to suspend and resume the game loop.
12. battleship
Create a terminal-based Battleship game where two players connect (P2P) via IP addresses to compete. The interface should be a split-screen TUI:
- one grid showing the player's own fleet placement and hits taken
- a "radar" grid tracking shots fired at the opponent.
Once connected, users place ships with rotation controls and compete under a time limit, where each player has a total bank of 5 minutes for the entire match; depleting this time results in an automatic loss regardless of the board state.
13. redditor
Download the news feed from a subreddit from Reddit (ex) and print the following information for each post: creation date, title, and link to post.
Command line options:
- the name of the subreddit
- the sort order: hot, new, top (default: hot)
Print only the new posts every N seconds (keep a list of posts that have already been printed).
14. Spotify controller
Build a terminal-based Spotify player that gives users full control over their music without leaving the command line. The application must be implement the following features:
- display a live dashboard showing the current track, volume, and a dynamic progress bar
- browse saved playlists and Liked Songs
- search for new tracks to add to the queue
- switch playback between active devices (like phone or PC)
The application must implement the API communication manually, building the integration from scratch without using existing Spotify libraries. Spotify Web API
15. sudoku
Create a terminal Sudoku game generates unique 9x9 Sudoku puzzles; The game must be able to:
- generate puzzle based on Easy, Medium, or Hard difficulty
- support keyboard navigation for data entry
- validate and higlights errors immediately
- tracks solving duration with an integrated timer
16. YouTube analytics reporter
Implement a command-line tool that takes a YouTube Channel ID and gathers comprehensive channel data, outputting a report in HTML format. The report must include the following information:
- number of subscribers, total views, video count
- detailed breakdown of popular videos by views, likes, and comments
- performance metrics: average views per video, like-to-view ratio, and engagement rate (likes + comments / views) for top content
- channel creation date and upload frequency (videos per week/month) References: Youtube API
17. json validator
Develop a CLI mini JSON validator that allows users to accept a text input and validate if it's proper JSON. The validator should return a message indicating if the JSON is valid or not and where the error is.
1. remove_duplicates
Given an input folder, identify all duplicate files (with the exact same content) in that folder and display a list to the user, asking them to decide which duplicates to delete. Based on the user's decision, the duplicate files will be either deleted or kept.
Example output:
The following files are identical:
1. <path>/Abc.txt
2. <path>/Test.123
3. <path>/file.log
Please select the file you want to keep [1..3] ? ___
2. hangman
Write an application where the user has to guess a specific word. The words will be read from a dictionary file and will belong to a certain category.
Upon running the application, the user chooses a category, and a random word from the selected category will be chosen. The user can guess one letter at a time. If they guess a letter correctly, the positions of that letter in the word will be displayed. The user is allowed to make a certain number of incorrect letter guesses (depending on the length of the word). During the game, the remaining number of attempts will be displayed.
In the end, the word and the number of unsuccessful attempts will be shown. Words will be saved in specific files for their respective categories. Additionally, scores will be recorded (in a separate file).
Example input: sport
Example output end:
Word: football
Incorrect guesses: 2
3. vcard
Make an application that can generate a vCard given an input json. The resulted file must be readable by tools that can read vCards.
The application should also be be able to read a vCard file and print the information in a human readable way.
4. ascii_table_gen
Read CSV or JSON files and generate an ASCII table that will be outputed to stdout or a file. For CSV files, make sure to support entries that include the separator.
Command line options:
- the input file
- the output file or stdout (default: stdout)
- if there's a separating line between content lines, like in the second example (default: no)
- how to align the cells (left, center, right) (default: left)
5. dir_size
Implement an application that, for a specific location, returns the total size of the files on disk. The script should provide the option to use filters on file names. Filtering can be done using regular expressions.
Example input: ./dir_size /path/to/directory --filter โ.*\.exeโ --filter โ.*\.dllโ
Example output: 1.1gb (1073772 bytes)
6. pass_gen
Write an application that generates and displays on the screen a string representing a password that meets the following conditions:
- It will have a length between 12 and 18 characters.
- It will start with an uppercase letter.
- It will contain alphanumeric characters and at least one symbol from the set ("!", "?", "#", "@").
Depending on a parameter given at the command line, the generated password will either be composed of random characters that satisfy the above conditions or be constructed from words in a dictionary saved on disk in a .txt file.
Example input:
./pass_gen
./pass_gen --dict dict.txt
7. folder_to_json
Create a tool that takes a folder path as a parameter and generates a JSON file with information about the directory structure received as a parameter. The output file will contain information about all files and subfolders inside, as well as statistics, like:
- the number of files
- the number of folders
- the number of files for each extension encounted
8. dont_touch_me
Create an app that will monitor changes to a file, and restore it to its original/hardcoded content after each change.
9. rcleaner
Make an app that iterates recursively over a folder, and:
- if it finds a rust project that has a
targetfolder, it will runcargo cleanin that folder - if it finds a
.vsfolder that looks like a Visual Studio folder, it will delete it - if it finds a folder that contains the cache directory tag, it will delete it.
10. take_a_break
Make an app that can receives an operation and a duration at command like, waits the duration, and executes the operation. Supported operations: shutdown/reboot/sleep/hibernate.
The app should work on at least 2 of the mainstream OSes (Linux, Windows, Mac).
Example command:
./take_a_break -s 1m -o hibernate
./take_a_break -s 1h -o sleep
11. password manager
Implement a CLI password manager that allows users to store credentials encrypted in a local JSON file (using serde and rand crates). The password manager should have the following commands implemented:
- add a new credential with a title, username, password
- view all credentials titles
- search for a credential
- delete a credential
./password-manager add <title> <username> <password>
./password-manager view
./password-manager search <title>
./password-manager delete <title>
12. markdown to HTML converter
Implement a CLI markdown to HTML converter that allows users to parse basic Markdown syntax (headers, bold, italics) and output the equivalent HTML.