First, a question
A quiz has a number of questions. From design, each question has a title and an answer which can be true or false. We also need to be able to track if a question has been answered or not β to determine score and if the quiz is complete.
This helps us to arrive at the following data structure for a question:
struct Question {
title: String,
answer: bool,
user_answer: Option<bool>
}
Put this piece of code in quiz/src/quiz.rs
.
What is an Option<>
? Why donβt we just use bool
for user_answer
? This is because the user may not have answered the question yet. So we use Option<>
to indicate that the value may or may not be present. Weβll see how to use it later.
We need implement two methods on the Question
struct to help us 1) know if the question has been answered and 2) know if the userβs answer is correct.
// ^ struct Question { ... }
impl Question {
fn is_answered(&self) -> bool {
// `is_some` is a method on `Option<>` that tells us if
// the `user_answer` is not `None`
self.user_answer.is_some()
}
fn is_correct(&self) -> bool {
// What is `Some`? Make a Google search π€·π½ββοΈ?
self.user_answer == Some(self.answer)
}
}
Notice that the methods are supposed to return a bool
but thereβs no return
keyword. In Rust, the last statement1 of a function without termination (ie, ;
) is a return value.
Then a quiz
A quiz is a collection of questions and also tracks the current question.
struct Quiz {
questions: Vec<Question>,
// What is `usize`? Make a Google search π€·π½ββοΈ?
current_index: usize
}
Letβs implement methods to
- get the current question
- move to the next question but only if the current question has been answered
- get the score of the quiz
Attempt to implement these methods yourself by referencing the Question
methods above. If you get stuck or done, see/compare the solution below:
Here are some pointers:
-
Iterator loops: https://doc.rust-lang.org/reference/expressions/loop-expr.html#iterator-loops
-
Method syntax: https://web.mit.edu/rust-lang_v1.25/β¦/method-syntax.html. Watch out for mutable
self
references.
The following is a concrete implementation of the methods. You can compare with yours.
// ^ struct Quiz { ... }
impl Quiz {
// Here we return a reference to the current question
// else we'll be making a `move` which the compiler will complain
// about.
// Try to remove the `&` on this line and the next and see what happens.
fn current_question(&self) -> &Question {
&self.questions[self.current_index]
}
// &mut means mutable reference
fn next_question(&mut self) -> &Question {
let count = self.questions.len() - 1;
if self.current_question().is_answered() && self.current_index < count {
self.current_index += 1;
}
self.current_question()
}
fn score(&self) -> usize {
// notice `mut`
let mut correct = 0;
for question in &self.questions {
if question.is_correct() {
correct += 1;
}
}
correct
}
}
mut
is used to indicate that the variable can be mutated. In Rust, you have to be intentional about mutation.
Exercise: more methods
You must have noticed we are missing some implementations. This is your exercise:
- Implement a method to go to the previous question:
fn prev_question(&mut self) -> Question
- Implement a method to answer the current question. It should also return true/false if the answer is correct or not:
fn answer(&mut self, answer: bool) -> bool
Weβll test our implementation in the next lesson.