结构体
和大多数语言一样,rust也提供了结构体。一个结构体定义如下所示:
代码语言:javascript复制struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
其中,struct是定义结构体的关键字,User是该结构体的名称,active,username,email,sign_in_count是结构体中的字段,并且给每个字段声明了类型。注意,定义结构体结束的时候没有分号。
创建结构体实例
上面只是定义了一个名为User的结构体,我们要创建相应的实例才能使用。在实例化结构体的时候需要注意:
- 初始化实例时,每个字段都需要进行初始化
- 初始化时的字段顺序不需要和结构体定义时的顺序一致
下面是实例化的一个例子:
代码语言:javascript复制let mut user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
访问结构体中的字段
和C/C 类似,访问结构体中的字段使用.运算符。例如:
代码语言:javascript复制user1.email = String::from("everyone@example.com"); // 写入
println!("{}", user1.email); // 读取
简化结构体创建
当函数参数和结构体字段同名时,可以直接使用缩略方式来创建结构体,例如:
代码语言:javascript复制fn build_struct(username:String, active: bool) -> User {
User { active, username, email: "zhangsan@example.com".to_string(), sign_in_count: 1 }
}
在build_struct函数中,直接将和结构体字段同名的函数参数使用在结构体初始化中即可完成同名字段的绑定。
更新结构体
有一种情况很常见:根据已有的结构体实例,创建新的结构体实例,例如根据已有的 user1 实例来构建 user2。这时候可以使用..user1
的方式来进行简化。例如:
let user2 = User{
username: "lisi".to_string(),
..user1
};
println!("{}", user2.username);
语法细节上,…user1之后没有逗号(,),并且必须在结构体初始化的末尾使用,另外需要注意虽然结构体的创建中没有使用=,而是使用:,但是本质上都是变量绑定的过程。user2实例,我们指定了username,其余字段从user1中获取,那么这会导致user1中的email所有权转移到user2的email字段中,除此之外,user1的其余字段是可以正常使用的。
元组结构体
结构体必须要有名称,但是结构体的字段可以没有名称,这种结构体长得很像元组,因此被称为元组结构体。元组结构体在你希望有一个整体名称,但是又不关心里面字段的名称时将非常有用。例如:
代码语言:javascript复制fn main() {
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
}
单元结构体
单元结构体没有任何字段,类似于接口。如果你定义一个类型,但是不关心该类型的内容, 只关心它的行为时,就可以使用 单元结构体。例如:
代码语言:javascript复制struct AlwaysEqual;
let subject = AlwaysEqual;
// 我们不关心 AlwaysEqual 的字段数据,只关心它的行为,因此将它声明为单元结构体,然后再为它实现某个特征
impl SomeTrait for AlwaysEqual {
}
结构体数据的所有权
前面我们定义的结构体中没有使用字符串切片引用,这是一个有意而为之的选择:因为我们想要这个结构体拥有它所有的数据,而不是从其它地方借用数据。因为结构体从其它对象借用数据是需要引入生命周期的。生命周期能确保结构体的作用范围要比它所借用的数据的作用范围要小。例如:
代码语言:javascript复制struct User {
username: &str,
email: &str,
sign_in_count: u64,
active: bool,
}
fn main() {
let user1 = User {
email: "someone@example.com",
username: "someusername123",
active: true,
sign_in_count: 1,
};
}
这段代码由于缺少声明周期的缘故,无法执行。声明周期也是一个复杂的概念,后续在学习。
使用 #[derive(Debug)]
来打印结构体的信息
在前文中,并没有直接打印结构体的实例,而是打印其中的字段。如果要直接打印结构体实例需要加上#[derive(Debug)]
属性。rust考虑到结构体比较复杂,没有Display特征,而是交给我们自己实现。但是Rust包含了打印出调试信息的功能,不过我们必须为结构体显式选择这个功能。为此,在结构体定义之前加上外部属性 #[derive(Debug)]
。例如:
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
// 打印结构体字段
println!("{}", rect1.height);
println!("{}", rect1.width);
// 简单打印
println!("rect1 is {:?}", rect1);
// 漂亮的风格
println!("rect1 is {:#?}", rect1);
}
使用dbg!宏
dbg! 宏接收一个表达式的所有权,打印出代码中调用 dbg! 宏时所在的文件和行号,以及该表达式的结果值,并返回该值的所有权。注意:调用 dbg! 宏会打印到标准错误控制台流(stderr),与 println! 不同,后者会打印到标准输出控制台流(stdout),当然了,目前我们的标准输入,标准输出,标准错误都是控制台。例如:
代码语言:javascript复制#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
// 使用dbg!宏
dbg!(&rect1);
}
参考资料
- rust语言圣经
- rust程序设计语言