Module D - Trait objects and Rust patterns
The BSN (Burgerservicennummer) is a Dutch personal identification number that somewhat resembles the US Social Security Number in its use. The BSN is a number that adheres to some rules. In this exercise, we will create a Rust type that guarantees that it represents a valid BSN.
D.1.A Newtype ⭐⭐
In this part we will implement the BSN number validation, as well as a fallible constructor.
A BSN is valid if and only if it matches the following criteria:
- It consists of 8 or 9 digits
- It passes a variant of the 11 check (elfproef (Dutch)):
For 8-digit BSNs, we concatenate a
0 to the end. The digits of the number are labeled as
For example: for BSN
A = 1,
B = 2,
C = 3, and so forth until
(9 × A) + (8 × B) + (7 × C) + (6 × D) + (5 × E) + (4 × F) + (3 × G) + (2 × H) + (-1 × I) must be a multiple of 11
exercises/D/1-bsn in your editor. You'll find the scaffolding code there, along with two files:
valid_bsns.incontaining a list of valid BSNs
invalid_bsns.incontaining a list of invalid BSNs.
Bsn::validate to make the
test_validation test case pass.
Bsn::try_from_string as well.
To try just the
test_validation test case, run:
cargo test -- test_validation
D.1.A Visitor with Serde ⭐⭐⭐
Next up is implementing the
serde::Deserialize traits, to support serialization and deserialization of
In this case, simply deriving those traits won't suffice, as we want to represent the
BSN as a string after serialization.
We also want to deserialize strings directly into
Bsns, while still upholding the guarantee that an instantiated
Bsn represents a valid BSN.
Therefore, you have to incorporate
Bsn::validate into the implementation of the deserialization visitor.
More information on implementing the traits:
If everything works out, all tests should pass.
D.2 Typestate 3D Printer ⭐⭐
An imaginary 3D printer uses filament to create all kinds of things. Its states can be represented with the following state diagram:
┌─────────────────┐ │ │ │ │ Reset │ Idle │◄────────────────────────────┐ ┌────────►│ │ │ │ │ │ │ │ │ │ │ │ └────────┬────────┘ │ │ │ │ │ │ │ │ │ Start │ │ │ │ │ ▼ │ │ ┌─────────────────┐ ┌────────┴────────┐ │ │ │ │ │ │ │ │ Out of filament │ │ Product │ │ Printing ├──────────────────► │ Error │ retrieved│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────┬────────┘ └─────────────────┘ │ │ │ │ Product ready │ │ │ ▼ │ ┌─────────────────┐ │ │ │ │ │ │ │ │ Product Ready │ └─────────┤ │ │ │ │ │ └─────────────────┘
The printer boots in Idle state. Once a job is started, the printer enters the Printing state. In printing state, it keeps on printing the product until either it is ready or the printer is out of filament. If the printer is out of filament, the printer goes into Error state, which it can only come out of upon device reset. If the product is ready, the printer goes to Product Ready state, and once the user retrieves the product, the printer goes back to Idle.
The printer can be represented in Rust using the typestate pattern as described during the lecture. This allows you to write a simple 3D printer driver. In
Printer3D struct is instantiated. Add methods corresponding to each of the traits, that simulate the state transitions by printing the state. A method simulating checking if the printer is out of filament is provided.
Of course, to make the printer more realistic, you can add more states and transitions.
D.3 Dynamic deserialization ⭐⭐
In this exercise, you'll work with dynamic dispatch to deserialize with
serde_yaml, depending on the file extension. The starter code is in
exercises/D/3-config-reader. Fix the todo's in there.
To run the program, you'll need to pass the file to deserialize to the binary, not to Cargo. To do this, run
cargo run -- <FILE_PATH>
config.yml should result in the
Config being printed correctly.