这个user1没设置mut, 故实例不可以改。
注意,整个实例必须是可变的;Rust 不允许我们仅将某些字段标记为可变的。如果你在创建结构体的时候使用了 mut,那么整个结构体都是可变的,所有的字段都可以被修改。
根据特定自动返回user
类似自己写了个构造函数。
Using the Field Init Shorthand
将函数参数命名为与结构字段同名是有意义的,但必须重复字段名称email
和username
变量有点乏味。因为参数名和结构字段名在清单,故可以使用field init 速记语法重写 build_user,使其行为完全相同但没有重复的email和username。
顺序可以打乱。在这里,我们正在创建User
结构的一个新实例,它有一个名为email
. 我们想将email
字段的值 设置为函数email
参数中的build_user
值。因为email
字段和email
参数同名,所以我们只需要写email
而不是email: email
。
使用其他实例创建新的值
更简单的写法如下。..
指定未显式设置的其余字段应与给定实例中的字段具有相同的值。
tuple structs
元组结构没有字段关联的名称,它们只有字段的类型。而相较于元组,在管理相同类型的数据时,有更加直观。当你想要给整个元组一个名字并将元组与其他元组区分开来时可以使用此结构。
例如,你可能会使用元组结构来表示颜色,并希望为每种颜色分配一个名字。你可以定义一个元组结构 Color,其中存储了三个 i32 类型的值,分别表示红,绿和蓝色分量。可以此来定义其他颜色。
注意 black 和 origin 值是不同类型,因为它们是不同元组结构体的实例。每个您定义的结构体都是自己的类型,即使结构体内的字段可能具有相同的类型。例如,接受类型 Color 的参数的函数不能作为 Point 类型的参数
没有任何字段的类单元(unit-like )结构
没有任何字段的结构被称为类单元结构,因为它们的行为类似于我们在“元组类型”部分()中提到的单元类型。当需要在某种类型上实现特征但没有要存储在类型本身中的任何数据时,类似单元的结构会很有用。
要定义AlwaysEqual
,我们使用struct
关键字、我们想要的名称,然后是分号。不需要大括号或圆括号!AlwaysEqual
然后我们可以用类似的方式在变量中获取一个实例subject
:使用我们定义的名称,不带任何大括号或圆括号。
结构体的所有权
不使用结构
计算由单独的宽度和高度变量指定的矩形的面积,相关性较差。将宽度和高度组合在一起会更具可读性和更易于管理
用元组进行重构
我们现在只传递一个参数。但是现在对具体的width和height没有明确的命名。可能会使人混淆。
使用结构重构(增加更多信息)
传递一个结构体引用(要是不想要ownership,传整体也行)给函数。我们想要借用结构而不是拥有它。这样,main
保留其所有权并可以继续使用rect1
,这就是我们&
在函数签名和调用函数的地方使用的原因。
使用派生特征添加有用的功能
我们看到的基本类型Display
都是默认实现的,因为只有一种方式可以向1
用户显示一个或任何其他基本类型。但是对于结构, println!
格式化输出的方式就不那么清晰了,因为有更多的显示可能性。
以调试模式打印,但我们必须明确选择让该功能可用于我们的结构。
修改后:
加强版的输出 {:#?}
trait
在 Rust 中,trait 是用来描述类型的一组特征的。trait 可以被用来定义共同的行为,并被多个类型实现。
一些常见的 trait 包括:
-
Clone
: 这个 trait 允许我们复制值,而不是移动它们。 -
Copy
: 这个 trait 允许我们在移动或覆盖值的同时保留一个副本。 -
Debug
: 这个 trait 允许我们用{:?}
格式化调试输出。 -
Eq
: 这个 trait 允许我们比较两个值是否相等。 -
Ord
: 这个 trait 允许我们比较两个值的大小。 -
PartialEq
: 这个 trait 允许我们比较两个值是否相等,但是类型不必完全匹配。 -
PartialOrd
: 这个 trait 允许我们比较两个值的大小,但是类型不必完全匹配。 -
Hash
: 这个 trait 允许我们使用值作为键值。
这些 trait 都是 Rust 标准库中内置的。你也可以自己定义新的 trait。
dbg!宏打印
并且它不需要取该表达式的引用(反而是将表达式的所有权交还给它)。它会打印出该dbg!宏调用在代码中的文件名和行号以及该表达式的值,然后返回该值的所有权。
调用 dbg! 宏会将信息输出到标准错误控制台流 (stderr),而 println! 则会输出到标准输出控制台流 (stdout)。这意味着,使用 dbg! 宏输出的信息会出现在终端的错误信息中,而使用 println! 输出的信息则出现在终端的正常输出中。
简单案例:
本案例:
Method Syntax
方法与函数类似:我们使用fn关键字和名称来声明它们,它们可以有参数和返回值,并且在从其他地方调用方法时会运行一些代码。与函数(独立的代码块,它可以在任何地方被调用)不同的是,方法在结构体的上下文中定义,它们的第一个参数总是self,表示调用方法的结构体的实例。
为了在 Rectangle 的上下文中定义函数,我们为 Rectangle 开始一个 impl(实现)块。此 impl 块中的所有内容都将与 Rectangle 类型相关联。然后,将 area 函数移动到 impl 大括号内,并在签名和函数体内的所有位置将第一个(在这种情况下,仅有)参数更改为 self。
&self实际上是
self: &Self。
方法的第一个参数必须命名为 self。 self 的类型必须是 Self。写&self而不是self(可以写),原因与在函数版本中使用 &Rectangle 相同:我们不想拥有所有权,只想读取结构体中的数据,而不是写入它。如果我们想在方法执行时更改调用方法的实例,则应使用 &mut self 作为第一个参数。拥有 self 的方法很少使用;通常使用此技术是因为方法将 self 转换为其他内容,并且您希望防止调用者在转换后使用原始实例。
使用方法而不是函数的主要原因,除了提供方法语法并且无需在每个方法的签名中重复 self 类型之外,还是为了组织。我们将所有可以使用类型实例的内容放在一个 impl 块中。
getters
方法名可以与字段名相同,当我们提供与字段同名的方法时,我们希望它只返回字段中的值而不做任何其他事情。像这样的方法称为getters,Rust 不会像其他一些语言那样自动为结构字段实现它们。Getters 很有用,因为您可以将字段设为私有,但将方法设为公开,从而作为类型的公共 API 的一部分启用对该字段的只读访问:
-> Operator
Rust 没有等价于 -> 运算符的东西;相反,Rust 有一个称为自动引用和解除引用的功能。调用方法是 Rust 中拥有此行为的少数地方之一。仅适用于方法的接收者,意思就是如果方法参数接的是&self,你直接穿rect也可以,会隐式转为&rect。有了这个特征,传方法直接无脑传就行。接收者来决定穿的具体模式
这是它的工作方式:当使用 object.something() 调用方法时,Rust 会自动添加 &、&mut 或 *,使 object 与方法的签名匹配。换句话说,以下内容是相同的:
p1.distance(&p2); (&p1).distance(&p2); 第一个看起来要干净得多。这种自动引用行为之所以能够工作,是因为方法具有明确的接收者——self 的类型。 给定方法的接收者和名称,Rust 可以明确地确定方法是读取(&self)、修改(&mut self)还是消耗(self)。 Rust 将借用隐式应用于方法接收者的事实是使所有权在实践中更加容易。
图1
图2
图3
更多参数的方法
相关函数
在 impl 块内定义的所有函数都被称为关联函数,因为它们与 impl 后面的类型相关联。我们可以定义没有 self 作为第一个参数的关联函数(因此不是方法),因为它们不需要类型的实例才能工作。我们已经使用了一个类似的函数:String::from 函数,它定义在 String 类型上。
非方法的关联函数通常用作构造函数,它们将返回 struct 的新实例。这些函数通常被称为 new,但 new 不是一个特殊的名称,也不是内置在语言中的。
多个impl
首先,可以使用多个 impl 块来定义不同的行为,其中每个 impl 块定义一组功能。这可以使类型的实现更加清晰,并使代码更容易阅读和维护。
其次,可以使用多个 impl 块来分离相关的功能,并使用不同的 impl 块来实现某个 trait 的不同版本。这可以让类型具有更多的灵活性,并允许更好地适应变化。
文章评论