【Rust自学】5.2. struct使用例(加打印调试信息)
对不起我都写到第8章了才发现我忘记发这一篇了,现在补上,不过这可能导致专栏的文章顺序有一点问题,但也只能将就着了。
喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
5.2.1. 例子需求
创建一个函数,计算长方形的面积,长和宽类型均为u32
且面积类型为u32
。
5.2.2. 普通解法
最简单的解法就是定义这个函数有两个参数:一个长一个宽,都为&u32
类型(例子中说了是u32
类型,并且这个场景下不需要函数获得数据所有权,所以使用引用在数据类型前加&
),在函数中返回长乘以宽的值就行。
fn main() {
let width = 30;
let length = 50;
println!("{}", area(&width, &length));
}
fn area(width: &u32, length: &u32) -> u32 {
width * length
}
输出:
1500
5.2.3. 元组解法
普通解法本身没有问题,但在可维护性有一个问题:长和宽是独立的参数,程序中的任何地方都不清楚这些参数是相关的。将宽度和高度组合在一起会更具可读性和更易于管理。对于数据的整合,使用元组再好不过(因为都是同一数据类型,所以在这里使用数组也是可以的)。
fn main() {
let rectangle = (30,50);
println!("{}", area(&rectangle));
}
fn area(dim:&(u32,u32)) -> u32 {
dim.0 * dim.1
}
输出:
1500
5.2.4. struct解法
元组解法虽然提升了可维护性,但代码的可读性变差了,因为如果不加注释没人知道元组的第一个数据是代表长还是代表宽(虽然对于计算面积来说无所谓,但是对于较大的项目来说很重要)。元组的元素是没有名字的,即使是元组结构体(上一篇文章中有讲),它里面的元素也是没有名字的。
那么那种数据结构可以把两个数据整合到一起并且分别赋名呢?没错,就是struct。
struct Rectangle {
width: u32,
length: u32,
}
fn main() {
let rectangle = Rectangle{
width: 30,
length: 50,
};
println!("{}", area(&rectangle));
}
fn area(dim:&Rectangle) -> u32 {
dim.width * dim.length
}
5.2.5.打印结构体的调试信息
接着上面的代码,如果再加一行直接打印rectangle
这个实例会怎么样呢?代码如下:
struct Rectangle {
width: u32,
length: u32,
}
fn main() {
let rectangle = Rectangle{
width: 30,
length: 50,
};
println!("{}", area(&rectangle));
println!("{}", rectangle); //直接打印实例
}
fn area(dim:&Rectangle) -> u32 {
dim.width * dim.length
}
输出:
error[E0277]: `Rectangle` doesn't implement `std::fmt::Display`
--> src/main.rs:12:20
|
12 | println!("{}", rectangle);
| ^^^^^^^^^ `Rectangle` cannot be formatted with the default formatter
|
= help: the trait `std::fmt::Display` is not implemented for `Rectangle`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
= 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)
先解释一下报错:println!
这个宏它可以执行很多格式化的打印。占位符{}
就是告诉println!
来使用std::fmt::Display
这个trait(理解成接口),类似于Python的toString
,而在报错信息中提到的就是Rectangle
并没有实现std::fmt::Display
这个trait,也就不能打印。
实际上,目前所讲的基础数据类型,默认都实现了std::fmt::Display
这个trait,因为它们的展示方式都比较单一,比如说把1打印出来,那程序只可能打印出阿拉伯数字1。但是对于Rectangle
,它里面有2个字段,是要都打印,还是打印width
,还是打印length
呢?可能性太多了,所以Rust并没有为struct默认实现std::fmt::Display
这个trait。
但如果我们继续往下看到这一行:
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
编译器提示我们可以使用{:?}
或者是{:#?}
来代替{}
。那就试试第一种:
struct Rectangle {
width: u32,
length: u32,
}
fn main() {
let rectangle = Rectangle{
width: 30,
length: 50,
};
println!("{}", area(&rectangle));
println!("{:?}", rectangle); //把`{}`改为`{:?}`
}
fn area(dim:&Rectangle) -> u32 {
dim.width * dim.length
}
还是报错了:
error[E0277]: `Rectangle` doesn't implement `Debug`
--> src/main.rs:12:22
|
12 | println!("{:?}", rectangle);
| ^^^^^^^^^ `Rectangle` cannot be formatted using `{:?}`
|
= help: the trait `Debug` is not implemented for `Rectangle`
= note: add `#[derive(Debug)]` to `Rectangle` or manually `impl Debug for Rectangle`
= 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 `Rectangle` with `#[derive(Debug)]`
|
1 + #[derive(Debug)]
2 | struct Rectangle {
|
但报错信息变了,上一回是没有实现std::fmt::Display
,这回是没有实现Debug
。Debug
和Display
一样也是一种格式化方法。继续往下看到note这行:
= note: add `#[derive(Debug)]` to `Rectangle` or manually `impl Debug for Rectangle`
编译提示我们添加#[derive(Debug)]
到代码中或是手动实现Debug
这个trait。这里使用前一种(后一种下一篇文章会讲):
#[derive(Debug)]
struct Rectangle {
width: u32,
length: u32,
}
fn main() {
let rectangle = Rectangle{
width: 30,
length: 50,
};
println!("{}", area(&rectangle));
println!("{:?}", rectangle);
}
fn area(dim:&Rectangle) -> u32 {
dim.width * dim.length
}
输出:
1500
Rectangle { width: 30, length: 50 }
这次就可以成功通过了。Rust本身包含了打印调试信息的功能(也就是debug信息的功能),但必须为自己代码中的结构体显式地选择这一功能,所以要在定义结构体前加上#[derive(Debug)]
这个注解。这种输出把结构体的名字、字段的名字及值都显示出来了。
有的时候结构体里有很多的字段,这时候{:?}
说打印出的横向排列的字段就没有那么易读。如果想要输出更加易读,那就把{:?}
改为{:#?}
:
#[derive(Debug)]
struct Rectangle {
width: u32,
length: u32,
}
fn main() {
let rectangle = Rectangle{
width: 30,
length: 50,
};
println!("{}", area(&rectangle));
println!("{:#?}", rectangle);
}
fn area(dim:&Rectangle) -> u32 {
dim.width * dim.length
}
输出:
1500
Rectangle {
width: 30,
length: 50,
}
这个输出中字段就是纵向排列,对于有很多字段的结构体来说更加易读。
实际上Rust提供了很多trait让我们可以进行derive
(派生),这些trait可以为自定义类型添加很多功能。所有的trait和它们的行为都可以在官方指南中找到,我把网址链接附在这里。
在上边的代码中就是让Rectangle
这个struct派生于Debug
这个trait,所以在打印时就可以使用调试模式。
再举个例子,假设你有一个表示点坐标的结构体:
#[derive(Debug, Clone, PartialEq)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let point1 = Point { x: 1, y: 2 };
let point2 = point1.clone();
println!("{:?}", point1); // 使用 Debug 特质打印 Point
assert_eq!(point1, point2); // 使用 PartialEq 特质比较两个 Point
}
在这个例子中:
#[derive(Debug)]
允许你使用{:?}
格式化规范来打印Point
结构体的实例。#[derive(Clone)]
允许你创建一个Point
实例的副本。#[derive(PartialEq)]
允许你比较两个Point
实例是否相等。