← Table of contents

1.3 Teaching Things to Speak

Asking a thing to describe itself is a reasonable wish, especially when something goes wrong. A task in the code will not answer such a request: the shape exists, the data exists, but there is no voice. Silence is not a refusal. Silence is when the question was never heard at all.

The Attempt

The fields are currently printed one by one. As the number of fields and tasks grows, that will become unwieldy quickly. It is more sensible to ask the task to describe itself as a whole. Rust has a special format flag for this - {:?}:

println!("{:?}", task);

The compiler responds:

error[E0277]: `Task` doesn't implement `Debug`
  --> src/main.rs:XX:XX
   |
XX |     println!("{:?}", task);
   |                      ^^^^ `Task` cannot be formatted using `{:?}`
   |
   = help: the trait `Debug` is not implemented for `Task`
   = note: add `#[derive(Debug)]` to `Task` or manually `impl Debug for Task`

What the Compiler Knows

{:?} is a request: “describe yourself in debug format.” For a type to fulfil that request, it must know how. The compiler cannot invent a description - it requires an explicit commitment.

In Rust, these commitments are called traits. A trait is a set of capabilities a type promises to provide. Debug is one of them: a type that implements Debug knows how to describe itself for debugging purposes. Task has made no such commitment yet.

Traits do a great deal in Rust - chapter 3.1 covers them in depth. For now, one thing is enough: without Debug, the compiler does not know how to handle {:?}.

The Enchantment

The compiler already gave the answer in the error message: add #[derive(Debug)] to Task.

#[derive(Debug)]
struct Task {
    id: u64,
    title: String,
    status: bool,
}

#[derive(Debug)] is an attribute that asks the compiler to generate a Debug implementation automatically. This works because all of Task’s fields - u64, String, bool - already know how to describe themselves. The compiler assembles their descriptions together.

Now {:?} works:

println!("{:?}", task);
Task { id: 1, title: "Buy coffee", status: false }

There is also {:#?} - the same format, but with line breaks and indentation:

println!("{:#?}", task);
Task {
    id: 1,
    title: "Buy coffee",
    status: false,
}

The task can describe itself. When something goes wrong, there will be something to print.

The complete tq code for this chapter is in 1-a-task/03-teaching-things-to-speak/.


Lore: Debug and Display

Debug is not the only trait for output. Rust has two:

println!("{}", task) still will not work: Task has no Display. It will appear later - when the task learns to speak in the user’s language, not just the developer’s.