1.6 What a Task Knows of Itself
A name is not a skill. The task carries a name, holds a state, and can describe itself. But knowledge of how to work with it lives outside - with the craftsman. While there is one piece and the craftsman is not tired, this is tolerable. The tolerable has a habit of becoming unbearable at the least convenient moment.
What You Need
After the previous chapter, the task has two helpers: create_task creates it,
task_complete completes it. That is a step forward: operations have names, knowledge about
the fields is not spread across main.
But both functions know about the task’s internal structure - its fields, Status::Todo and
Status::Done. That knowledge lives outside the type. With two functions, it is tolerable.
Every new function will duplicate it on its own.
In Rust, a type’s behaviour lives in an impl block. Give the task three abilities: creating
itself, becoming complete, and answering a question about its state. create_task becomes
Task::new, and task_complete becomes the method complete.
The Build
create_task and task_complete are no longer needed - their logic moves into impl Task.
Add the block immediately after struct Task:
impl Task {
fn new(id: u64, title: &str) -> Task {
Task {
id,
title: String::from(title),
status: Status::Todo,
}
}
}
impl Task opens the block where Task’s behaviour is defined. new is a function with no
self parameter: it does not work with an existing task, it creates a new one. It is called
with ::, not a dot: Task::new(1, "Buy coffee").
Replace create_task in main:
let mut task = Task::new(1, "Buy coffee");
let mut - the task will change in the next step; this is familiar from the previous chapter.
Add the complete method to the same impl Task block:
fn complete(&mut self) {
self.status = Status::Done;
}
complete takes &mut self - it modifies the task, so let mut at the call site is
required. It is already there. The call: task.complete().
And one more method - is_done:
fn is_done(&self) -> bool {
match self.status {
Status::Done => true,
Status::Todo => false,
}
}
is_done takes &self - read only. The match is familiar: two variants, two answers.
The Result
#[derive(Debug)]
enum Status {
Todo,
Done,
}
#[derive(Debug)]
struct Task {
id: u64,
title: String,
status: Status,
}
impl Task {
fn new(id: u64, title: &str) -> Task {
Task {
id,
title: String::from(title),
status: Status::Todo,
}
}
fn complete(&mut self) {
self.status = Status::Done;
}
fn is_done(&self) -> bool {
match self.status {
Status::Done => true,
Status::Todo => false,
}
}
}
fn main() {
let mut task = Task::new(1, "Buy coffee");
println!("Task #{}: {}", task.id, task.title);
println!("{:#?}", task);
task.complete();
println!("{:#?}", task);
if task.is_done() {
println!("done");
} else {
println!("todo");
}
}
Task #1: Buy coffee
Task {
id: 1,
title: "Buy coffee",
status: Todo,
}
Task {
id: 1,
title: "Buy coffee",
status: Done,
}
done
make ci passes. Three methods are ready - chapter 1.11 will add tests for them.
The complete
tqcode for this chapter is in1-a-task/06-what-a-task-knows-of-itself/.
Lore: &self, &mut self, and self
In the previous chapter the same three forms looked like Task, &Task, &mut Task - in
methods they become self, &self, &mut self.
Methods in Rust have three ways to receive themselves as the first parameter:
&self- shared borrow. The value is not changed; the caller keeps it.&mut self- mutable borrow. The method can modify the value; the caller must havelet mut.self- transfer of ownership. The method consumes the value; after the call it is gone. Rare, but it appears - for example, in methods that convert a type into another.
new takes no self at all: it is not a method but an associated function. It belongs to
the type, not to an instance - hence Task::new, not task.new.
Lore: Macros - a Distant Introduction
In real code, is_done is often written differently:
fn is_done(&self) -> bool {
matches!(self.status, Status::Done)
}
matches! is a macro: a name with an exclamation mark. Macros in Rust are a large topic in
their own right - there will be more on them later. For now, one thing is enough: an
exclamation mark after a name means a macro, not a regular function. matches! here does
exactly what the match does - just shorter.