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}");
}
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
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:?}");
}
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 {
|
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:?}");
}
Woohoo! Now it runs!
Car { maker: "Maker1", model: "Model1", year_of_making: 2023, max_speed_kph: 190 }
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,
}
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);
}
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,
}
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)