Appearance
structured code
结构化代码
rust有2种创建结构化数据类形的方式:
- 结构体
struct
:像c/c++那样的结构体,用于保存数据 - 枚举
enum
:像OCaml,数据可以是几种类形之一
结构体和枚举都可以有若干实现块impl,用于定义相应类形的方法
结构体(Structs)和枚举(Enums)的一个特性,即它们都可以拥有多个impl
块来定义方法。
结构体 (
Structs
): 在Rust中,结构体是一种组合多个数据片段的方式。struct Point { x: i32, y: i32, }
枚举 (
Enums
): 枚举允许你定义一种数据,该数据可以取多个不同的形式。enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), }
实现块 (
impl
blocks):impl
块用于定义结构体或枚举的方法。你可以为一个结构体或枚举定义多个impl
块。impl Point { // 这是第一个impl块 fn distance_from_origin(&self) -> f64 { (self.x.pow(2) + self.y.pow(2)).sqrt() as f64 } } impl Point { // 这是第二个impl块 fn translate(&mut self, dx: i32, dy: i32) { self.x += dx; self.y += dy; } }
在上述代码中,我们为
Point
结构体定义了两个不同的impl
块,每个块中有一个方法。
结构体的声明
具名字段的结构体 (Named Fields)
这是最常见的结构体形式,其中每个字段都有一个明确的名称。
struct User {
username: String,
email: String,
age: u32,
active: bool,
}
元组结构体 (Tuple Structs)
元组结构体(Tuple Structs)是Rust中的一种特殊类型的结构体。它们的特点是字段没有具体的名称,只有类型,这种结构体看起来像元组,但它们有一个具体的类型名称。
struct Color(u8, u8, u8);
struct Point(f64, f64);
定义元组结构体 要定义元组结构体,你需要提供一个结构体名称,后面跟随圆括号,并在圆括号内列出字段的类型。
struct Point3D(f64, f64, f64);
struct Color(u8, u8, u8);
struct Pair(i32, i32);
创建元组结构体的实例 就像普通元组一样,你可以使用圆括号来创建元组结构体的实例。
let origin = Point3D(0.0, 0.0, 0.0);
let red = Color(255, 0, 0);
let coordinates = Pair(10, 20);
访问元组结构体的字段 你可以使用.后跟字段的索引来访问元组结构体的字段,就像访问普通元组的元素一样。
let x = origin.0;
let y = origin.1;
let z = origin.2;
let r = red.0;
let g = red.1;
let b = red.2;
为什么使用元组结构体? 尽管元组结构体和普通元组在许多方面都很相似,但有时你可能希望为某个特定的数据结构提供一个明确的名称,而不仅仅是一个匿名元组。元组结构体允许你为数据提供类型别名,这有助于提高代码的可读性和意图的清晰性。
例如,虽然(f64, f64, f64)和(u8, u8, u8)在类型上是不同的,但它们在语义上没有明确的区别。使用Point3D和Color作为元组结构体名称,可以明确地区分这两种数据结构的用途和意图。
单元结构体 (Unit Structs)
单元结构体(Unit Structs)是Rust中的一种特殊类型的结构体。与元组结构体和常规结构体不同,单元结构体没有任何字段。它们的定义不包含任何内容,只是一个名称。
定义单元结构体
单元结构体的定义非常简单,只需要一个名称:
struct MyUnitStruct;
如你所见,它看起来就像是一个结构体名称,后面跟着一个分号,没有任何其他内容。
为什么使用单元结构体?
可能会问,没有字段的结构体有什么用呢?以下是一些使用单元结构体的常见场景:
类型标记:单元结构体可以作为一个明确的类型标记。这在泛型编程中尤其有用,当你需要一个没有数据的类型,但仍然想区分其他类型时。
Trait实现:在Rust中,你可以为任何类型实现trait,包括单元结构体。这意味着你可以使用单元结构体来组织特定的方法或行为,而不必关心数据。
trait SayHello { fn say_hello(&self); } struct HelloUnit; impl SayHello for HelloUnit { fn say_hello(&self) { println!("Hello from HelloUnit!"); } }
Opaque类型:在某些情况下,你可能想隐藏实际的数据实现,而只提供一个类型标识。单元结构体可以在这样的场景中被用作一个不透明的类型标识。
创建和使用单元结构体
创建单元结构体的实例非常简单:
let unit_instance = MyUnitStruct;
由于单元结构体没有任何字段,所以你不能访问或修改其内容。然而,如果你为它实现了方法或trait,你可以调用这些方法。
总之,虽然单元结构体可能看起来不像传统的数据结构,但它们在Rust中是有用的,尤其是在需要类型标记或特定的trait实现,但不需要实际数据时。
创建结构体实例
一旦你声明了结构体,你可以使用它来创建该结构体的实例:
// 使用具名字段的结构体
let user1 = User {
username: String::from("Alice"),
email: String::from("alice@example.com"),
age: 30,
active: true,
};
// 使用元组结构体
let black = Color(0, 0, 0);
let origin = Point(0.0, 0.0);
可变性
默认情况下,结构体实例是不可变的。但是,通过添加mut
关键字,你可以创建一个可变的结构体实例:
let mut user2 = User {
username: String::from("Bob"),
email: String::from("bob@example.com"),
age: 25,
active: false,
};
user2.age = 26; // 修改age字段的值
这就是Rust中结构体的基本声明和使用方法。结构体是Rust中非常强大的自定义数据类型,允许你将相关数据组合在一起,并为其定义方法和关联函数
<!--在Rust中,当我们谈论"可变性"时,我们实际上是指变量的绑定,而不是结构体本身。-->
结构体定义(无论是常规结构体、元组结构体还是单元结构体)本身并不具有可变性。它只是定义了一个数据结构。但当你创建一个结构体的实例并为其绑定一个变量时,这个绑定可以是可变的或不可变的。
以下是如何创建可变和不可变的结构体实例的示例:
struct Point {
x: i32,
y: i32,
}
// 创建一个不可变的Point实例
let p1 = Point { x: 0, y: 0 };
// 创建一个可变的Point实例
let mut p2 = Point { x: 1, y: 1 };
// 由于p2是可变的,我们可以修改它的字段
p2.x = 2;
p2.y = 2;
// 但我们不能修改p1的字段,因为它是不可变的
// p1.x = 3; // 这会导致编译错误
所以,结论是:你可以为结构体实例的绑定添加mut关键字以使其可变,这允许你修改该实例的字段。但这并不改变结构体定义本身的性质。
结构体的访问
在Rust中,结构体的字段默认是私有的,这意味着它们只能在定义结构体的当前模块中访问。
但你可以使用pub
关键字来明确地使字段公开,从而允许外部模块访问它。
- 默认的私有字段
当你创建一个结构体时,它的字段默认是私有的。
mod my_module {
pub struct Person {
name: String,
age: u32,
}
pub fn create_person(name: String, age: u32) -> Person {
Person { name, age }
}
}
在上面的代码中,Person
结构体的name
和age
字段是私有的。这意味着你不能从my_module
模块外部直接访问这些字段。
- 使用
pub
使字段公开
通过在字段名前加上pub
关键字,你可以使字段公开。
mod my_module {
pub struct Person {
pub name: String,
pub age: u32,
}
pub fn create_person(name: String, age: u32) -> Person {
Person { name, age }
}
}
现在,name
和age
字段是公开的,可以从my_module
模块外部访问。
- 访问结构体的字段
访问结构体的字段非常简单。只需使用.
符号,后跟字段的名称。
let person = my_module::create_person(String::from("Alice"), 30);
println!("Name: {}", person.name);
println!("Age: {}", person.age);
注意:
- 尽管可以通过
pub
关键字公开结构体的字段,但在许多情况下,为了封装和保护数据,最好保持字段为私有,并提供公开的方法来获取或修改这些字段。 - 当你在定义结构体的模块中工作时,可以直接访问其私有字段。
结构体的访问权限
在 Rust 中,访问权限(或称为可见性)决定了结构体、其字段、函数、模块等元素是否可以从其他模块或外部代码中访问。以下是关于结构体和其访问权限的一些关键点:
默认私有:Rust 中的所有项(包括结构体字段)默认都是私有的。这意味着,除非你明确地使用
pub
关键字来公开它们,否则它们只能在定义它们的模块中访问。结构体的公开性:如果你想从外部模块或代码中创建或明确引用结构体,你需要使用
pub
关键字公开它:pub struct Person { name: String, age: u32, }
在这里,
Person
结构体是公开的,但其字段name
和age
仍然是私有的。字段的公开性:如果你想从外部访问或修改结构体的某个字段,你需要公开那个字段:
pub struct Person { pub name: String, pub age: u32, }
现在,
name
和age
字段都是公开的,可以从任何地方访问和修改。在模块中的访问权限:你可以在模块中定义结构体,并根据需要设置其可见性。例如,如果你在一个子模块中定义一个私有结构体,它只能在该子模块中访问,而不能在父模块或其他模块中访问。
关联函数和方法的公开性:与结构体和其字段类似,结构体的关联函数和方法也是默认私有的。如果你想从外部调用它们,你需要使用
pub
关键字公开它们。
结构体的更新语法
结构体的更新语法允许从一个已存在的结构体实例创建一个新实例,同时只改变部分字段的值。这种语法非常有用,特别是当结构体有很多字段,而你只想修改其中的一小部分时。
使用 ..
语法可以指定你希望使用另一个结构体实例的字段值。
以下是一个例子来说明这种语法:
struct Point {
x: i32,
y: i32,
z: i32,
}
fn main() {
let p1 = Point { x: 1, y: 2, z: 3 };
// 使用结构体更新语法从p1创建p2,只改变x字段
let p2 = Point { x: 5, ..p1 };
println!("p2: x = {}, y = {}, z = {}", p2.x, p2.y, p2.z);
}
在上面的例子中,p2
会有 x
值为 5,而 y
和 z
的值会与 p1
相同。