← Table of contents

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 tq code for this chapter is in 1-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:

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.