定义并实例化结构体
结构体的每一部分可以是不同类型。但不同于元组,结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些名字,结构体比元组更灵活:不需要依赖顺序来指定或访问实例中的值。
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
let mut user2 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
user2.email = String::from("anotheremail@example.com");
fn build_user(email: String, username: String) -> User {
User {
email: email,
username: username,
active: true,
sign_in_count: 1,
}
}
注意整个实例必须是可变的;Rust 并不允许只将某个字段标记为可变。
我们可以在函数体的最后一个表达式中构造一个结构体的新实例,来隐式地返回这个实例。
为函数参数起与结构体字段相同的名字是可以理解的,但是不得不重复 email 和 username
字段名称与变量有些啰嗦,幸运的是,有一个方便的简写语法!
变量与字段同名时的字段初始化简写语法
fn build_user(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}
因为 email 和 username 参数与结构体字段同名,因为 email 字段与 email 参数有着相同的名称,则只需编写 email 而不是 email: email 。
使用结构体更新语法从其他实例创建实例
使用旧实例的大部分值但改变其部分值来创建一个新的结构体实例通常是很有帮助的。这可
以通过 结构体更新语法(struct update syntax)实现。
let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
active: user1.active,
sign_in_count: user1.sign_in_count,
};
let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
..user1
};
使用结构体更新语法为一个 User 实例设置新的 email 和 username 值,不过其余值来自 user1 变量中实例的字段。
使用没有命名字段的元组结构体来创建不同的类型
可以定义与元组(在第三章讨论过)类似的结构体,称为 元组结构体(tuple structs)。
元组结构体有着结构体名称提供的含义,但没有具体的字段名,只有字段的类型。
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
black 和 origin 值的类型不同,因为它们是不同的元组结构体的实例。
元组结构体实例类似于元组:可以将其解构为单独的部分,也可以使用 . 后跟索引来访
问单独的值,等等。
没有任何字段的类单元结构体
可以定义一个没有任何字段的结构体!它们被称为 类单元结构体(unit-like structs)
因为它们类似于 () ,即 unit 类型。类单元结构体常常在你想要在某个类型上实现 trait 但不
需要在类型中存储数据的时候发挥作用
结构体数据的所有权
可以使结构体存储被其他对象拥有的数据的引用,不过这么做的话需要用上 生命周期
(lifetimes)。
生命周期确保结构体引用的数据有效性跟结构体本身保持一致。如果你尝试在结构体中存储一个引用而不指定生命周期将是无效的。具体可查阅生命周期概念。
一个使用结构体的示例程序
//普通变量定义和传递
let width1 = 30;
let height1 = 50;
fn area(width: u32, height: u32) -> u32{...}
//元组类型定义和传递
let rect1 = (30, 50);
fn area(dimensions: (u32, u32)) -> u32 {...}
//结构体类型定义和传递
struct Rectangle {
width: u32,
height: u32,
}
fn area(rectangle: &Rectangle) -> u32 {...}
通过派生 trait 增加实用功能
在调试程序时打印出 Rectangle实例来查看其所有字段的值。
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!("rect1 is {}", rect1);
}
error[E0277]: `Rectangle` doesn't implement `std::fmt::Display`
= 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) in
stead
目前为止见过的基本类型都默认实现了Display, 结构体并没有提供一个 Display 实现。因为基本类型都默认实现了Display,但内部成员是否都已经实现了Display,编译器并不知道。
按照错误提示尝试在{} 中加入 :? 指示符告诉 println! 我们想要使用叫做 Debug 的输出格式。Debug 是一个 trait,它允许我们以一种对开发者有帮助的方式打印结构体,以便当我们调试代码时能看到它的值。这样调整后再次运行程序。见鬼了!仍然能看到一个错误:
error[E0277]: `Rectangle` doesn't implement `std::fmt::Debug`
= help: the trait `std::fmt::Debug` is not implemented for `Rectangle`
= note: add `#[derive(Debug)]` or manually implement `std::fmt::Debug`
Rust 确实 包含了打印出调试信息的功能,不过我们必须为结构体显式选择这个功能。为此,
在结构体定义之前加上 #[derive(Debug)] 注解。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!("rect1 is {:?}", rect1);
}
现在我们再运行这个程序时,就不会有任何错误,并会出现如下输出:
rect1 is Rectangle { width: 30, height: 50 }
当我们有一个更大的结构体时,能有更易读一点的输出就好了,为此可以使用 {:#?} 替
换 println! 字符串中的 {:?} 。如果在这个例子中使用了 {:#?} 风格的话,输出会看起来
像这样:
rect1 is Rectangle {
width: 30,
height: 50
}
Rust 为我们提供了很多可以通过 derive 注解来使用的 trait,他们可以为我们的自定义类型
增加实用的行为。
方法语法
方法 与函数类似,不过方法与函数是不同的,因为它们在结构体的上下文中被定义(或者是枚举或 trait 对象的上下文),并且它们第一个参数总是 self ,它代表调用该方法的结构体实例。
定义方法
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!(
"The area of the rectangle is {} square pixels.",
rect1.area());
}
&self 的理由跟在函数版本中使用 &Rectangle 是相同的:我们并不想获取所有权,只希望能够读取结构体中的数据,而不是写入。
如果想要在方法中改变调用方法的实例,需要将第一个参数改为 &mut self。
仅仅使用 self 作为第一个参数来使方法获取实例的所有权是很少见的;这种技术通常用在当方法将 self 转换成别的实例的时候,这时我们想要防止调用者在转换之后使用原始的实例。
-> 运算符到哪去了?
Rust 并没有一个与 -> 等效的运算符;相反,Rust 有一个叫 自动引用和解引用(automatic referencing and dereferencing)的功能
他是这样工作的:当使用 object.something() 调用方法时,Rust 会自动为 object 添加 & 、 &mut 或 * 以便使 object 与方法签名匹配。
是因为方法有一个明确的接收者———— self 的类型。在给出接收者和方法名的前提下,Rust 可以明确地计算出方法是仅仅读取( &self ),做出修改( &mut self )或者是获取所有权( self )。
带有更多参数的方法
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
关联函数
允许在 impl 块中定义 不 以 self 作为参数的函数。这被称为 关联函数(associated functions),因为它们与结构体相关联。它们仍是函数而不是方法,因为它们并不作用于一个结构体的实例,就是静态方法。
关联函数经常被用作返回一个结构体新实例的构造函数
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle { width: size, height: size }
}
}
使用结构体名和 :: 语法来调用这个关联函数:比如 let sq = Rectangle::square(3); 。这个方法位于结构体的命名空间中: :: 语法用于关联函数和模块创建的命名空间。
其实 等同于C++的静态函数,单例instance。
多个 impl 块
每个结构体都允许拥有多个 impl 块
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
但是在这里没有大多意义,泛型和trait 时会看到实用的多 impl 块的用例。