结构体
一个 struct 就是一些字段的集合。
字段是一个与数据类型相关联的数据值。它的值可以是基本类型或者是结构体类型。
它的定义就像是给编译器的蓝图,告诉编译器如何在内容中布局彼此相邻的字段。
struct SeaCreature {
// String 是个结构体
animal_type: String,
name: String,
arms: i32,
legs: i32,
weapon: String,
}
方法调用
与函数(function)不同,方法(method)是与特定数据类型关联的函数。
静态方法-属于某个类型,调用时使用 :: 运算符
动态方法-属于某个类型的实例,调用时使用 . 运算符
fn main() {
// 使用静态方法来创建一个String实例
let s = String::from("Hello world!");
// 使用实例来调用动态方法
println!("{} is {} characters long.", s, s.len());
}
内存
Rust 程序有3个存放数据的内存区域:
- 数据内存 - 对于固定大小和静态(即在整个程序生命周期中都存在)的数据。比如上一例子中的文本(例如“Hello world”),该文字的字节只能读取,因为它们位于该区域中。编译器对于这类数据做了很多优化,由于位置已知且固定,因此通常认为编译器使用起来非常快。
- 栈内存 - 对于在函数中声明为变量的数据。在函数调用期间,内存的位置不会改变,因为编译器可以优化代码,所以栈数据使用起来也比较快。
- 堆内存 - 对于在运行时创建的数据。此区域中的数据可以添加、移动、删除、调整大小等。因为它的动态特性,通常认为它使用起来比较慢,但是它允许更多创造性的内存使用。当数据添加到该区域时,我们称其为分配。从本区域中删除数据我们称之为释放。
在内存中创建数据
当我们在代码中实例化一个结构体时,我们的程序会在内存中并排创建相关的字段数据。
当我们通过制定所有字段值的方式来实例化时:
结构体 { … };
结构体字段可以通过 . 运算符来获取。
struct SeaCreature {
animal_type: String,
name: String,
arms: i32,
legs: i32,
weapon: String,
}
fn main() {
// SeaCreature 的数据在栈上
let ferris = SeaCreature {
// String 结构体也在栈上
// 但也存放了一个数据在栈上
animal_type: String::from("螃蟹"),
name: String::from("Ferris"),
arms: 2,
legs: 4,
weapon: String::from("大钳子"),
};
let sarah = SeaCreature {
animal_type: String::from("章鱼"),
name: String::from("Sarah"),
arms: 8,
legs: 0,
weapon: String::from("无"),
};
println!(
"{} 是只 {}。 它有 {} 只胳膊 {} 只腿。还有一个{}。",
ferris.name, ferris.animal_type, ferris.arms, ferris.legs, ferris.weapon
);
println!(
"{} 是只 {}。 它有 {} 只胳膊 {} 只腿。它没有杀伤性武器~",
sarah.name, sarah.animal_type, sarah.arms, sarah.legs
);
}
这个例子中的内存详情:
- 引号内的文本数据是只读数据(“例如ferris”),它位于数据内存区。
- 函数调用 String::from 创建一个结构体 String , 该结构体与SeaCreature的字段并排放置在栈中。字符串容器通过如下步骤表示可更改的文本:
- 在堆上创建可修改文本的内存。
- 将堆中存储对象的内存位置的引用存储在 String 结构体中。
- 最后,我们的两个朋友 Ferris 和 Sarah 是有在程序中总是固定的位置的数据结构,所以它们被放在栈上
类元组结构体
简洁起见,可以创建像元组一样被使用的结构体。
struct Location(i32, i32);
fn main() {
// 这仍然是一个在栈上的结构体
let loc = Location(42, 32);
println!("{}, {}", loc.0, loc.1);
}
类单元结构体
结构体也可以没有任何字段。
就像第一章中提到的, 一个unit是一个空元组()的别称。这就是为什么,此类结构体被称为类单元。
这种类型的结构体很少用到。
struct Marker;
fn main() {
let _m = Marker;
}
枚举
枚举允许使用 enum 关键字创建一个新类型,该类型的值可以包括几个带标记的元素。
match 有助于确保对多有可能的枚举值进行彻底的处理,使其成为确保高质量代码的强大工具。
#![allow(dead_code)] // this line prevents compiler warnings
enum Species {
Crab,
Octopus,
Fish,
Clam
}
struct SeaCreature {
species: Species,
name: String,
arms: i32,
legs: i32,
weapon: String,
}
fn main() {
let ferris = SeaCreature {
species: Species::Crab,
name: String::from("Ferris"),
arms: 2,
legs: 4,
weapon: String::from("claw"),
};
match ferris.species {
Species::Crab => println!("{} is a crab.", ferris.name),
Species::Octopus => println!("{} is a Octopus", ferris.name),
Species::Fish => println!("{} is a fish", ferris.name),
Species::Clam => println!("{} is a clam", ferris.name),
}
}
带数据的枚举
enum 的元素可以有一个或者多个数据类型,从而使其表现得像C语言中的联合。
当使用 match 对另一个 enum 进行模式匹配时,可以将变量名称绑定到每个数据值。
enum 的内存细节:
- 枚举数据的内存大小等于它最大元素的大小。此举是为了让所有可能的值都能存入相同的内存空间。
- 除了每个元素数据类型(如果有)之外,每个元素还有个数字值,用于表示他是哪个标签。
其他细节
- Rust的 enum 也被成为标签联合(tagged-union)
- 把类型组合成一种新类型,着就是人们所说的Rust具有代数类型的含义。
#![allow(dead_code)]
enum Species { Crab, Octopus, Fish, Clam }
enum PoisonType { Acidic, Painful, Lethal }
enum Size { Big, Small }
enum Weapon {
Claw(i32, Size),
Poison(PoisonType),
None
}
struct SeaCreature {
species: Species,
name: String,
arms: i32,
legs: i32,
weapon: Weapon,
}
fn main() {
// SeaCreature's data is on stack
let ferris = SeaCreature {
// String struct is also on stack
// but holds a reference to data on heap
species: Species::Crab,
name: String::from("Ferris"),
arms: 2,
legs: 4,
weapon: Weapon::Claw(2, Size::Small)
};
match ferris.species {
Species::Crab => {
match ferris.weapon {
Weapon::Claw(num_claws, size) => {
let size_description = match size {
Size::Big => "big",
Size::Small => "small",
};
println!("ferris is a crab with {} {} claws", num_claws, size_description)
},
_ => println!("ferris is a crab with some other weapon")
}
}
},
_ => println!("ferris is some other animal"),
}
}