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 theFrom
trait you have already seen, specifically meant to parse data from strings. When implemented, it enables the&str::parse()
function, used here inmain()
. - To implement
FromStr
you will have to pick anErr
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:
- 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. - Add a function that returns the hashed version of a password account. You can use the
sha1
crate for this. - 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.