结构体
结构体允许你包装和命名多个相关的值,从而形成一个有意义的聚合。比较类似之前的元组,但有些许不同。
可以这样定义一个结构体:
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}其中每一行名字和类型构成一个字段(field)。
可以这样实例化它:
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};如果实例是可变的,可以通过点号更改它的值:
let mut user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
user1.email = String::from("anotheremail@example.com");注意,Rust 要求所有字段的可变性一致。也就是说,要么整个实例都是可变的,要么都不可变。
可以使用一个函数填充初始值:
fn build_user(email: String, username: String) -> User {
User {
email: email,
username: username,
active: true,
sign_in_count: 1,
}
}当参数名和字段名相同时,可以进一步简化:
fn build_user(email: String, username: String) -> User {
User {
email: email,
username: username,
active: true,
sign_in_count: 1,
}
}基于其他实例创建
当我们想基于其他实例修改的时候,我们可以这样写:
let user2 = User {
email: String::from("another@example.com"),
..user1
};
// 等价于
let user2 = User {
active: user1.active,
username: user1.username,
email: String::from("another@example.com"),
sign_in_count: user1.sign_in_count,
};..user1 必须放在最后,其他字段的顺序不重要。
这种语法类似带有 =
的赋值,因为移动了数据。在这个例子中,我们创建 user2
后不再使用 user1,因为 user1 中的
username 字段被移到了 user2 中。如果我们为
username 和 email 都赋了新值,那么
user1 仍然有效。因为 active 和
sign_in_count 都实现了 Copy trait。
元组结构体
使用元组结构体(tuple structs),可以达到为元组指定别名的效果。比如:
struct Color(i32, i32, i32)
struct Point(i32, i32, i32)
fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
}注意,Color 和 Point 的类型并不兼容。Rust
是强类型的,不会允许两个结构体长得像,就允许相互赋值。
在其他方面,元组结构体很像元组,也允许解构声明和点操作符访问。
类单元结构体
Rust 允许你这样做:
struct AlwaysEqual;
fn main() {
let subject = AlwaysEqual;
}定义一个结构体而没有字段,也叫类单元结构体(unit-like
structs),因为这类似 ()。
这通常用于想要在某个类型上实现
trait,但不想存储数据的场景。这将在后面涉及。
结构体的所有权
在 User 的结构体定义中,我们使用 String
而不是 &str
类型。因为我们希望结构体拥有该数据,只要结构体有效,对应的字段也有效。
可以使结构体储存引用,但需要生命周期(lifetimes)。生命周期保证字段有效性和结构体本身保持一致。这将在后面详细讲解。
方法
有以下代码:
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!(
"The area of the rectangle is {} square pixels.",
area(&rect1)
);
}
fn area(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height
}我们可以将长宽信息存储至 Rectangle,并使用
area 函数计算。然而正如文章Kotlin - 面向 IDE
的编程语言中所说的:
最终 Kotlin 形成一长串调用
a.map {...}.sorted().toString,而 Python 不断嵌套str(sorted(map(...)))。这有什么好处?只需要在表达式末尾添加一个点,IDE 的选择列表就会激活,并且帮你找到想要的内容。而在 Python 中,你需要「提前」知道所有函数名,并且要将所有圆括号、方括号、大括号一一匹配。
Kotlin 允许你使用拓展函数,Swift 也有 extension。而 Rust
更为激进——所有方法都是单独的实现,并与结构体关联。
使用 impl
我们可以声明结构体的实现,并且如之前所说,我们可以有多个实现:
struct Rectangle {
length: u32,
width: u32,
}
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle {
length: size,
width: size,
}
}
fn area(&self) -> u32 {
self.width * self.length
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.length > other.length
}
}
impl Rectangle {
fn can_hold_least(&self, other: &Rectangle, area: u32) -> bool {
self.can_hold(other) && self.area() > area
}
}其中 &self
指名了方法的接收者,实质上是一个简写。可以理解为
self: &Rectangle。若没有
self,则代表该方法是静态的,仅仅是绑定在该命名空间下,可以通过
:: 访问:
fn main() {
let sq = Rectangle::square(10);
println!("The size of square is {}", sq.area());
}
->运算符呢?在 C / C++ 中,我们需要有两个不同的运算符来调用方法:
.直接在对象上调用,->在对象指针上调用,这时还需要解引用指针。若object_ptr是一个指针,那么object_ptr->something()就和(*objcet_ptr).something()一样。Rust 没有
->运算符,相反,Rust 会自动引用和解引用(automatic referencing and dereferencing)。方法调用是为数不多有该行为的地方。Rust 会自动为
object添加&&mut或*以便和接收者签名匹配。这两者等价:p1.distance(&p2); (&p1).distance(&p2);Rust 可以通过接收者签名,明确的知道方法是要只读(
&self)、做出修改(&mut self)或是获得所有权(self)。
枚举
枚举(enumerations),也叫 enums,允许你列举可能的成员(varints)来定义一个类型。
Rust 的枚举更类似 Haskell 中的代数数据类型(algebraic data types),Kotlin 中的密封接口(sealed interface)。而不只是一个表示有限集合的对象。
要定义枚举很简单:
enum IpAddrKind {
V4,
V6,
}可以通过 :: 创建它们:
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;很容易想到这样来存储 IP 地址的值:
struct IpAddr {
kind: IpAddrKind,
address: String,
}
let home = IpAddr {
kind: IpAddrKind::V4,
address: String::from("127.0.0.1"),
};
let loopback = IpAddr {
kind: IpAddrKind::V6,
address: String::from("::1"),
};在大多数语言中,我们都可以这样做,即将枚举值作为属性。
Rust 允许一种更方便的写法:
enum IpAddr {
V4(String),
V6(String),
}
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));我们可以直接将数据附加在枚举的每个成员上,就不需要额外的结构体了。
同时,每个枚举成员的签名不必相同:
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));标准库的写法类似这样:
pub enum IpAddr {
V4(Ipv4Addr),
V6(Ipv6Addr),
}
struct Ipv4Addr {
// ...
}
struct Ipv6Addr {
// ...
}可以将任意类型的数据放入枚举,字符、数字、结构体,甚至另一个枚举,都是可以的。
枚举的可能性很多,来看下另一个例子:
enum Message {
Quit, // unit-like
Move { x: i32, y: i32 }, // struct
Write(String), // tuple struct
ChangeColor(i32, i32, i32), // tuple struct
}对于枚举,我们也可以声明对应的实现:
impl Message {
fn call(&self) { /* ... */ }
}Option
Rust 中没有空值。而是使用该枚举替代。
它的定义很简单:
enum Option<T> {
None,
Some(T),
}T 是泛型,这在后面详细讲解。这里只需要知道
T 可以装任何类型即可。
Option 会被自动导入,因此可以直接用 None 或
Some 创建 Option。
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;Debug Trait
这里我们不具体介绍 trait,只讲解如何使用。
通过派生 Debug trait,我们可以打印调试信息:
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
println!("rect1 is {:?}", rect1);
// rect1 is Rectangle { width: 30, height: 50 }
println!("rect1 is {:#?}", rect1);
// rect1 is Rectangle {
// width: 30,
// height: 50,
// }
}{:?} 为简短 debug 样式,{:#?} 为较长 debug
样式。
与 Debug 对应的有 Display
trait,用于显示给用户。
match
match 用于更好地处理枚举。包括
Option。如这样:
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}枚举要求分支穷尽,所以可以用于返回值/赋值:
impl Coin {
fn to_value(&self) -> u8 {
return match self {
Coin::Penny => {
println!("Penny!");
1
}
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
};
}
}
fn main() {
Coin::Penny.to_value();
}在 match 遇到有值的枚举时,我们这样拿到对应的值:
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
impl Coin {
fn to_value(&self) -> u8 {
return match self {
Coin::Penny => {
println!("Penny!");
1
}
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}", state);
25
}
};
}
}
fn main() {
Coin::Quarter(UsState::Alaska).to_value();
}当我们不想处理所有分支时,可以使用 _(相当于
else):
impl Coin {
fn is_penny(&self) -> bool {
return match self {
Coin::Penny => true,
_ -> false
};
}
}if let
if let 可以看作 match
的一种特殊形式,只处理一个操作。
let config_max = Some(3u8);
match config_max {
Some(max) => println!("The maximum is configured to be {}", max),
_ => (),
}可以改写为:
let config_max = Some(3u8);
if let Some(max) = config_max {
println!("The maximum is configured to be {}", max);
}也可以添加 else:
let mut count = 0;
if let Coin::Quarter(state) = coin {
println!("State quarter from {:?}!", state);
} else {
count += 1;
}以上,这就是对 Rust 结构体和枚举的介绍了。
