Packaging Rust code

Consider the following structure representing user accounts. Each account has two String fields: a public username and a private password.

#![allow(unused)]
fn main() {
pub struct Account {
    pub username: String,
    password: String,
}
}

Exercise 1.a: implement the following associated functions to create an Account from a pair of string( slice)s and parse one from an existing string:

use std::str::FromStr;

impl Account {
    /// Create a new account with a given username and password.
    fn new(username: &str, password: &str) -> Self {
        todo!()
    }
}

impl FromStr for Account {
    /// Create a new account by parsing its data from a string in the form "USERNAME:PASSWORD".
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        todo!()
    }
}

fn main() {
    let user1 = Account::new("zack", "cne3bfvkw5eerv30jy3990yz0w");
    let user2 = "sam:bf6ddgabh6jghr89beyg3gn7e8".parse::<Account>().unwrap();
    let user1_name = &user1.username;
    let user2_name = &user2.username;
    println!("let's welcome our new users: {user1_name} and {user2_name}");
    // let user1_pwd = &user1.password; // error[E0616]: field `password` of struct `Account` is private
}

A few points worth nothing:

  • The FromStr trait is a variant of the From trait you have already seen, specifically meant to parse data from strings. When implemented, it enables the &str::parse() function, used here in main().
  • To implement FromStr you will have to pick an Err type.
  • The todo! macro is very useful for code prototyping; it will make your code panic at runtime, but pass type checking. have a look at its documentation for details.

When done make sure that the above main function runs without panicking.

Exercise 1.b: put your code into a new package called accountmgr containing two crates: one library crate (implementing the type alias and the functions new_account and parse_account) and one binary crate containing the main function. Make sure your crates build and that the binary runs without panicking.

Now try to uncomment the last line of main, to make sure the module system does not allow you to access private fields from outside the defining module.

Remember: you now have two completely separate crates: a library crate with a public API that does not export the password field of the Account structure; and a binary crate using it as client code via that API.

Exercise 1.c: (optional) in the next few sections you will test the functionalities of your accountmgr crate, which for now is quite bare bone. To make things more interesting you can extend it by adding the following functionalities, with the API you please:

  1. Add a separate constructor function that randomizes password creation instead of taking one as input. For this you can use the rand crate and implement various "strong password" strategies to produce high-entropy passwords.
  2. Add a function that returns the hashed version of a password account. You can use the sha1 crate for this.
  3. Add a batch constructor fn from_file(filename: &Path) -> Vec<Self> to read account data from file, one account per line. Here is a file with many accounts (~20k) that you can use for testing purposes.

Later on, when asked to test accountmgr in the remainder of this lab session, also add tests for these additional functionalities! If you decide not to do this exercise right now, but finish early with the rest of the lab assignments, come back to this one later, add missing functionalities above, add corresponding tests, etc., until done.