Skip to content

structured code

结构化代码

rust有2种创建结构化数据类形的方式:

  • 结构体struct:像c/c++那样的结构体,用于保存数据
  • 枚举enum:像OCaml,数据可以是几种类形之一

结构体和枚举都可以有若干实现块impl,用于定义相应类形的方法

结构体(Structs)和枚举(Enums)的一个特性,即它们都可以拥有多个impl块来定义方法。

  1. 结构体 (Structs): 在Rust中,结构体是一种组合多个数据片段的方式。

    struct Point {
        x: i32,
        y: i32,
    }
  2. 枚举 (Enums): 枚举允许你定义一种数据,该数据可以取多个不同的形式。

    enum Message {
        Quit,
        Move { x: i32, y: i32 },
        Write(String),
        ChangeColor(i32, i32, i32),
    }
  3. 实现块 (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;

如你所见,它看起来就像是一个结构体名称,后面跟着一个分号,没有任何其他内容。

为什么使用单元结构体?

可能会问,没有字段的结构体有什么用呢?以下是一些使用单元结构体的常见场景:

  1. 类型标记:单元结构体可以作为一个明确的类型标记。这在泛型编程中尤其有用,当你需要一个没有数据的类型,但仍然想区分其他类型时。

  2. Trait实现:在Rust中,你可以为任何类型实现trait,包括单元结构体。这意味着你可以使用单元结构体来组织特定的方法或行为,而不必关心数据。

    trait SayHello {
        fn say_hello(&self);
    }
    
    struct HelloUnit;
    
    impl SayHello for HelloUnit {
        fn say_hello(&self) {
            println!("Hello from HelloUnit!");
        }
    }
  3. 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关键字来明确地使字段公开,从而允许外部模块访问它。

  1. 默认的私有字段

当你创建一个结构体时,它的字段默认是私有的。

mod my_module {
    pub struct Person {
        name: String,
        age: u32,
    }

    pub fn create_person(name: String, age: u32) -> Person {
        Person { name, age }
    }
}

在上面的代码中,Person结构体的nameage字段是私有的。这意味着你不能从my_module模块外部直接访问这些字段。

  1. 使用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 }
    }
}

现在,nameage字段是公开的,可以从my_module模块外部访问。

  1. 访问结构体的字段

访问结构体的字段非常简单。只需使用.符号,后跟字段的名称。

let person = my_module::create_person(String::from("Alice"), 30);
println!("Name: {}", person.name);
println!("Age: {}", person.age);

注意:

  • 尽管可以通过pub关键字公开结构体的字段,但在许多情况下,为了封装和保护数据,最好保持字段为私有,并提供公开的方法来获取或修改这些字段。
  • 当你在定义结构体的模块中工作时,可以直接访问其私有字段。

结构体的访问权限

在 Rust 中,访问权限(或称为可见性)决定了结构体、其字段、函数、模块等元素是否可以从其他模块或外部代码中访问。以下是关于结构体和其访问权限的一些关键点:

  1. 默认私有:Rust 中的所有项(包括结构体字段)默认都是私有的。这意味着,除非你明确地使用 pub 关键字来公开它们,否则它们只能在定义它们的模块中访问。

  2. 结构体的公开性:如果你想从外部模块或代码中创建或明确引用结构体,你需要使用 pub 关键字公开它:

    pub struct Person {
        name: String,
        age: u32,
    }

    在这里,Person 结构体是公开的,但其字段 nameage 仍然是私有的。

  3. 字段的公开性:如果你想从外部访问或修改结构体的某个字段,你需要公开那个字段:

    pub struct Person {
        pub name: String,
        pub age: u32,
    }

    现在,nameage 字段都是公开的,可以从任何地方访问和修改。

  4. 在模块中的访问权限:你可以在模块中定义结构体,并根据需要设置其可见性。例如,如果你在一个子模块中定义一个私有结构体,它只能在该子模块中访问,而不能在父模块或其他模块中访问。

  5. 关联函数和方法的公开性:与结构体和其字段类似,结构体的关联函数和方法也是默认私有的。如果你想从外部调用它们,你需要使用 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,而 yz 的值会与 p1 相同。