DEV Community

Cover image for Learning Rust πŸ¦€: 11 - Debugging custom types/Structs
Fady GA 😎
Fady GA 😎

Posted on

Learning Rust πŸ¦€: 11 - Debugging custom types/Structs

In this short article, we will see how we can print our structs / custom types for debugging proposes building on the knowledge from the last article. This article is so short that it doesn't need a table of contents πŸ˜‰. But don't get me wrong, it is still an important topic. Let's begin.

⚠️ Remember!

You can find all the code snippets for this series in its accompanying repo

If you don't want to install Rust locally, you can play with all the code of this series in the official Rust Playground that can be found on its official page.

⚠️⚠️ The articles in this series are loosely following the contents of "The Rust Programming Language, 2nd Edition" by Steve Klabnik and Carol Nichols in a way that reflects my understanding from a Python developer's perspective.

⭐ I try to publish a new article every week (maybe more if the Rust gods πŸ™Œ are generous 😁) so stay tuned πŸ˜‰. I'll be posting "new articles updates" on my LinkedIn and Twitter.

We will start by creating our custom "Car" type and try to print it:

struct Car {
    maker: String,
    model: String,
    year_of_making: u16,
    max_speed_kph: u16,
}
fn main() {
    let my_car = Car {
        maker: String::from("Maker1"),
        model: String::from("Model1"),
        year_of_making: 2023,
        max_speed_kph: 190,
    };

    println!("{my_car}");
}
Enter fullscreen mode Exit fullscreen mode

This won't go as we have anticipated! We will receive the following compile error:

rror[E0277]: `Car` doesn't implement `std::fmt::Display`
  --> src/main.rs:15:15
   |
15 |     println!("{my_car}");
   |               ^^^^^^^^ `Car` cannot be formatted with the default formatter
   |
   = help: the trait `std::fmt::Display` is not implemented for `Car`
   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
Enter fullscreen mode Exit fullscreen mode

Let's break it down. the println! macro implements formatters when using the curly brackets "{}" within it. For primitive types like the integer 1, it is implemented by default. After all, there aren't that many ways you want to display the number 1 to the user πŸ€·β€β™‚οΈ. But for custom types, Rust doesn't know how it should format them. Should it print it as a list, comma separated, some of its properties or all of them? So, rust won't take that guess and the Display formatting must be provided in order to print this type.

But we also get a note withing the error message, we may be able to use {:?} in order to "Display" our type, so let's try that:

struct Car {
    maker: String,
    model: String,
    year_of_making: u16,
    max_speed_kph: u16,
}
fn main() {
    let my_car = Car {
        maker: String::from("Maker1"),
        model: String::from("Model1"),
        year_of_making: 2023,
        max_speed_kph: 190,
    };

    println!("{my_car:?}");
}

Enter fullscreen mode Exit fullscreen mode

Aaaand we get a compilation error ... again πŸ€¦β€β™‚οΈ! This time with a different issue.

error[E0277]: `Car` doesn't implement `Debug`
  --> src/main.rs:15:15
   |
15 |     println!("{my_car:?}");
   |               ^^^^^^^^^^ `Car` cannot be formatted using `{:?}`
   |
   = help: the trait `Debug` is not implemented for `Car`
   = note: add `#[derive(Debug)]` to `Car` or manually `impl Debug for Car`
   = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider annotating `Car` with `#[derive(Debug)]`
   |
1  + #[derive(Debug)]
2  | struct Car {
   |
Enter fullscreen mode Exit fullscreen mode

Now Rust complains about our Car type doesn't implement the Debug trait (we will learn all about traits later). But the message error contains a usefull tip this time, note: add '#[derive(Debug)]' to 'Car' or manually 'impl Debug for Car'. It turns out that Rust doesn't provide debug printing out-of-the-box for our custom types. We have to opt-in to that functionality. So, let's do what the tip suggests:

#[derive(Debug)]
struct Car {
    maker: String,
    model: String,
    year_of_making: u16,
    max_speed_kph: u16,
}
fn main() {
    let my_car = Car {
        maker: String::from("Maker1"),
        model: String::from("Model1"),
        year_of_making: 2023,
        max_speed_kph: 190,
    };

    println!("{my_car:?}");
}
Enter fullscreen mode Exit fullscreen mode

Woohoo! Now it runs!

Car { maker: "Maker1", model: "Model1", year_of_making: 2023, max_speed_kph: 190 }
Enter fullscreen mode Exit fullscreen mode

And if we've used the {:#?} operator, our custom Car type will be pretty printed.

Car {
    maker: "Maker1",
    model: "Model1",
    year_of_making: 2023,
    max_speed_kph: 190,
}
Enter fullscreen mode Exit fullscreen mode

And that is one way to print our custom types for debugging. The other one is to use the dbg! macro which differs for the println! we know and love as it sends its output to the stderr terminal stream as opposed to println! which sends its output to the stdout terminal stream. One other reason is that dbg! takes ownership of any passed values to it then returns them back as opposed to println! which only takes a reference of the value.

So, building upon what we have so far, we can use dbg! in our previous example like so:

#[derive(Debug)]
struct Car {
    maker: String,
    model: String,
    year_of_making: u16,
    max_speed_kph: u16,
}
fn main() {
    let my_car = Car {
        maker: String::from("Maker1"),
        model: String::from("Model1"),
        year_of_making: 2023,
        max_speed_kph: dbg!(4 * 50),
    };
    dbg!(&my_car);
}
Enter fullscreen mode Exit fullscreen mode

We will get the following output:

[src/main.rs:13] 4 * 50 = 200
[src/main.rs:18] &my_car = Car {
    maker: "Maker1",
    model: "Model1",
    year_of_making: 2023,
    max_speed_kph: 200,
}
Enter fullscreen mode Exit fullscreen mode

dgb! prints the line where it was called and prints the expression and its value. For the first dbg!, it's perfectly fine to write it as we did because as we've mentioned, it returns back the value of the expression we've passed to it. And for the second dbg! call, we've passed a reference of my_car as we didn't want for it to take ownership over our type.

And that's it πŸ™‚. In the next article, we will talk about enums and pattern matching ... Good stuff πŸ‘ŒπŸ‘Œ! See you then πŸ‘‹.

Top comments (0)