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 print counted 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 database
  • search <name>: prints all the names and the urls that contains name in 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