rust程序设计(3)结构体相关概念和疑问

发布时间 2023-11-13 23:14:51作者: azoux

结构体

// 如何定义结构体
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}
// 如何使用结构体
let user = User {
	active: true,
  username: String::from("someusername123"),
  email: String::from("someone@example.com"),
  sign_in_count: 1,
}
// 解构
// 这里有个需要注意的是,如果某个类型没有实现Copy trait
// 那么就会传递所有权,比如这里的email,所以在定义完user2以后,user变量就不能再使用了
// 如果我们额外传入了email,那么user就还能使用
// 另一个问题:结构的位置会影响最后的值吗?就像js的对象结构,我们可以在下面覆盖掉上面解构对象的某些属性
// 在Rust中,结构(destructuring)不是用来“覆盖”值的,而是用来“重组”已存在的值到一个新的变量。这跟JavaScript的对象解构赋值不同,后者允许你在解构的同时覆盖之前对象的属性。
/**
好的,想象一下你有一个玩具箱子,里面有很多玩具。现在你想要一个新的箱子,而且你想把除了最爱的小黄鸭之外的所有玩具都放进新箱子里。在Rust中,我们可以用一个特殊的魔法词“..”,告诉大人我们想这么做。
所以我们对大人说:“请把我的所有玩具,除了小黄鸭,都移到新的箱子里去。”大人就会帮你做这件事。但是一旦玩具移到了新箱子里,你就不能再从旧箱子里拿玩具了,因为它们已经不在那里了。这就像当你把玩具借给朋友后,你就不能再玩那些借出去的玩具了。
而且,你不能像在画画时那样,先画一个太阳,然后又在上面画一个大风筝盖住太阳。在Rust里,一旦你把玩具放到了新箱子,你就决定好了谁去了新箱子,不能改变主意了。
这样的魔法规则帮助确保你的玩具箱子井井有条,每个玩具都知道它应该在哪个箱子里,就像确保你的房间整洁一样。这样你就不会弄丢玩具,也能更容易找到想玩的玩具。
*/
let user2 = User {
	username: String::from("hahaha"),
	...user
}

// rust的整个结构体实例都必须是可变的,才能修改其中的属性,rust不允许只有某些属性是可变的

// build_user 函数返回的User实例,是可变的吗
/**
在Rust中,可变性是由变量的绑定决定的,而不是由函数返回的值决定的。
这意味着,当你的build_user函数返回一个User实例时,它返回的是一个不可变的User实例。
如果你想要修改这个User,你需要在使用它的地方将它绑定到一个可变变量上。
*/
fn build_user(email: String, username: String) -> User {
    User {
        active: true,
        username: username,
        email: email,
        sign_in_count: 1,
    }
}

rust的可不可变是针对变量而不是值的吗?

正是如此!在Rust的世界里,可变性(mutability)是关于变量绑定,而不是值本身。想象一下,你的玩具可以放在两种不同的箱子里:一个是用胶带封死的箱子(不可变的),另一个是可以随时打开加东西的箱子(可变的)。玩具(值)本身没变,但根据你放它的箱子的类型,你可能可以改变箱子里的玩具集合。

所以,在Rust中,当你声明一个变量时,你可以选择它是不是mut的,就是选择用哪种箱子。一旦你决定了,只有mut箱子里的玩具(值)才能被换掉或者修改。如果你试图改变一个用胶带封死的箱子里的玩具,Rust会阻止你,确保你不会意外地弄乱你的玩具集合。这个规则是Rust确保程序安全的方式之一,避免了数据在你不知情的情况下被更改。

这样的设计让Rust程序的行为变得非常可预测,并且减少了程序中出现错误的机会。而且,这还帮助Rust管理内存,因为它可以确切地知道何时某个值不再被需要,以及何时它可以被安全地回收或重新使用。

元组结构体

// 注意,Color和Point是不同的类型,即使其中的元素个数和类型一样
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
}

没有任何字段的类单元结构体

没有任何字段的类单元结构体有什么用呢?

好比是小朋友的想象游戏,有时候你会拿个香蕉当电话玩,对吧?虽然它就是个香蕉,但在你的游戏里它就变成了电话。在Rust的世界里,一个没有任何字段的结构体就像是那个香蕉电话,它本身没啥用,但你给它赋予了意义。

就像你可以想象香蕉是电话一样,程序员有时需要一些东西来代表某些特别的事情,即使那个东西本身什么都没有。比如说,我们可能有一个空的结构体来表示游戏中的一个事件,它本身不保存任何信息,但它的存在就代表了某个事件发生了。

还有,就像你玩耍时可能会用一个看不见的"盒子"来装你想象中的宝贝一样,程序员也可能会用这种结构体来做出某些选择。例如,它们可以用来打开或关闭软件的某些功能,就像是开关一样。

另外,这种结构体也像是游戏规则里的“它”,比如“你是它”游戏中的“它”。它代表了一个标记或者一个特定的角色,即使它自己没有任何东西。

所以,即使是空空如也的结构体,在Rust这个大型游戏中也有自己的角色和用途。这就是它们的魔法之处!

让我们来看一个Rust中的代码例子,这个例子里我们将使用一个没有任何字段的结构体来做一些有趣的事情。想象一下,我们有一个游戏,游戏里有一些特殊的时刻,比如“游戏开始”或“游戏结束”。我们可以用不同的结构体来代表这些时刻,哪怕这些结构体里面什么都没有!

这里有一个小小的代码片段:

// 定义一个没有字段的结构体,叫做 `GameEvent`。
struct GameStart; // 游戏开始事件
struct GameEnd; // 游戏结束事件

// 我们可以定义一个函数来处理游戏事件。
// 注意这个函数如何接受不同的事件结构体作为参数。
fn process_game_event(event: impl GameEventTrait) {
    // 在这个函数里,我们可以根据事件的类型来做不同的事情。
    event.print_details();
}

// 让我们为这个结构体实现一个Trait,这个Trait允许我们打印出事件的细节。
trait GameEventTrait {
    fn print_details(&self);
}

// 实现 'GameStart' 结构体的 'GameEventTrait'。
impl GameEventTrait for GameStart {
    fn print_details(&self) {
        println!("The game has started!");
    }
}

// 实现 'GameEnd' 结构体的 'GameEventTrait'。
impl GameEventTrait for GameEnd {
    fn print_details(&self) {
        println!("The game has ended!");
    }
}

// 现在我们可以创建这些事件的实例并处理它们。
let start = GameStart;
let end = GameEnd;

// 处理这些事件
process_game_event(start);
process_game_event(end);

在这个例子中,我们定义了两个没有任何字段的结构体:GameStartGameEnd。它们都实现了一个叫做 GameEventTrait 的trait,这个trait有一个方法 print_details,用于输出一个关于事件的消息。

然后我们有一个 process_game_event 函数,它接受任何实现了 GameEventTrait 的类型的实例。这意味着我们可以传入 GameStartGameEnd 的实例。

当我们调用这个函数并传入 GameStartGameEnd 的实例时,它会打印出相应的开始或结束游戏的消息。

这样的结构体很适合用于事件处理、状态机、消息传递等场景,在这些场景中,标记或者控制流的存在比持有数据更重要。