结构体
结构体允许你包装和命名多个相关的值,从而形成一个有意义的聚合。比较类似之前的元组,但有些许不同。
可以这样定义一个结构体:
struct User {
: bool,
active: String,
username: String,
email: u64,
sign_in_count}
其中每一行名字和类型构成一个字段(field)。
可以这样实例化它:
let user1 = User {
: String::from("someone@example.com"),
email: String::from("someusername123"),
username: true,
active: 1,
sign_in_count};
如果实例是可变的,可以通过点号更改它的值:
let mut user1 = User {
: String::from("someone@example.com"),
email: String::from("someusername123"),
username: true,
active: 1,
sign_in_count};
.email = String::from("anotheremail@example.com"); user1
注意,Rust 要求所有字段的可变性一致。也就是说,要么整个实例都是可变的,要么都不可变。
可以使用一个函数填充初始值:
fn build_user(email: String, username: String) -> User {
{
User : email,
email: username,
username: true,
active: 1,
sign_in_count}
}
当参数名和字段名相同时,可以进一步简化:
fn build_user(email: String, username: String) -> User {
{
User : email,
email: username,
username: true,
active: 1,
sign_in_count}
}
基于其他实例创建
当我们想基于其他实例修改的时候,我们可以这样写:
let user2 = User {
: String::from("another@example.com"),
email..user1
};
// 等价于
let user2 = User {
: user1.active,
active: user1.username,
username: String::from("another@example.com"),
email: user1.sign_in_count,
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 {
: u32,
width: u32,
height}
fn main() {
let rect1 = Rectangle {
: 30,
width: 50,
height};
println!(
"The area of the rectangle is {} square pixels.",
&rect1)
area(;
)}
fn area(rectangle: &Rectangle) -> u32 {
.width * rectangle.height
rectangle}
我们可以将长宽信息存储至 Rectangle
,并使用
area
函数计算。然而正如文章Kotlin - 面向 IDE
的编程语言中所说的:
最终 Kotlin 形成一长串调用
a.map {...}.sorted().toString
,而 Python 不断嵌套str(sorted(map(...)))
。这有什么好处?只需要在表达式末尾添加一个点,IDE 的选择列表就会激活,并且帮你找到想要的内容。而在 Python 中,你需要「提前」知道所有函数名,并且要将所有圆括号、方括号、大括号一一匹配。
Kotlin 允许你使用拓展函数,Swift 也有 extension
。而 Rust
更为激进——所有方法都是单独的实现,并与结构体关联。
使用 impl
我们可以声明结构体的实现,并且如之前所说,我们可以有多个实现:
struct Rectangle {
: u32,
length: u32,
width}
impl Rectangle {
fn square(size: u32) -> Rectangle {
{
Rectangle : size,
length: size,
width}
}
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
或*
以便和接收者签名匹配。这两者等价:.distance(&p2); p1&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 {
: IpAddrKind,
kind: String,
address}
let home = IpAddr {
: IpAddrKind::V4,
kind: String::from("127.0.0.1"),
address};
let loopback = IpAddr {
: IpAddrKind::V6,
kind: String::from("::1"),
address};
在大多数语言中,我们都可以这样做,即将枚举值作为属性。
Rust 允许一种更方便的写法:
enum IpAddr {
String),
V4(String),
V6(}
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
我们可以直接将数据附加在枚举的每个成员上,就不需要额外的结构体了。
同时,每个枚举成员的签名不必相同:
enum IpAddr {
u8, u8, u8, u8),
V4(String),
V6(}
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 {
, // unit-like
Quit{ x: i32, y: i32 }, // struct
Move Write(String), // tuple struct
i32, i32, i32), // tuple struct
ChangeColor(}
对于枚举,我们也可以声明对应的实现:
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 {
: u32,
width: u32,
height}
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 {
+= 1;
count }
以上,这就是对 Rust 结构体和枚举的介绍了。