Rust的Struct结构体和方法

定义Struct

如下方式定义一个Struct结构体:

1
2
3
4
5
struct Person{
name: String,
age: u32,
email: String,
}

定义Struct后,可创建其实例对象:

1
2
3
4
5
6
7
8
9
10
11
fn main(){
let user1 = Person{
name: String::from("junmajinlong"),
email: String::from("[email protected]"),
age: 23,
};
println!(
"name: {}, age: {}, email: {}",
user1.name, user1.age, user1.email
);
}

Rust调试输出

上面示例中使用println!()输出user1实例的各字段值,这样可以查看实例各属性的信息,但比较麻烦。

更方便的方式应该是直接输出struct实例,例如println!("{}", user1)。实际上,如果指定的类型实现了Display(就像Ruby中定义了to_s一样),那么可直接输出struct。

即使没有实现Display,Rust的println!()也支持两种调试输出的语法{:?}{:#?},它们可以根据默认的格式输出数据类型。它们的作用就像Ruby里的p方法和pp方法一样。

但是,Rust不能直接使用这两个调试输出语法,它要求在目标struct的前面加上一行#[derive(Debug)]

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#[derive(Debug)]

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

fn main() {
let user1 = Person {
name: String::from("junmajinlong"),
email: String::from("[email protected]"),
age: 23,
};
println!("{:?}", user1);
println!("{:#?}", user1);
}

输出结果:

1
2
3
4
5
6
Person { name: "junmajinlong", age: 23, email: "[email protected]" }
Person {
name: "junmajinlong",
age: 23,
email: "[email protected]",
}

构造struct的简写方式

当要构造的struct实例的字段值来自于变量,且这个变量名和字段名相同,则可以简写该字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct Person{
name: String,
age: u32,
email: String,
}

fn main(){
let name = String::from("junmajinlong");
let email = String::from("[email protected]");

let user1 = Person{
name, // 简写,等价于name: name
email, // 简写,等价于email: email
age: 23,
};
}

有时候会基于一个struct实例构造另一个struct实例,Rust允许通过..xx的方式来简化构造语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
let name = String::from("junmajinlong");
let email = String::from("[email protected]");
let user1 = Person{
name,
email,
age: 23,
};

let mut user2 = Person{
name: String::from("gaoxiaofang"),
email: String::from("[email protected]"),
..user1
};

上面的user2借用了user1的age字段,即user2.age也是23。

另外,user2之所以可以【借用】user1的age字段,是因为age字段是u32类型,它存放在栈中,可以Copy。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let name = String::from("junmajinlong");
let email = String::from("[email protected]");
let user1 = Person{
name,
email,
age: 23,
};

let mut user2 = Person{
name: String::from("gaoxiaofang"),
..user1
};

// println!("{}", user1.email); // 报错,user1.email字段值的所有权已借给user2
// println!("{}", user1); // 报错
println!("{}", user1.name); // 正确
println!("{}", user1.age); // 正确

如果确实要借用user1的email属性,可以使用..user1.clone()先拷贝堆内存中的user1,这样就不会借用原始的user1中的email所有权。

1
2
3
4
let user2 = Person{
name: String::from("ggg"),
..user1.clone()
}

元组结构体

可以定义没有字段名的struct结构体,称为元组结构体。

例如:

1
2
3
4
5
struct Color(i32, i32, i32); 
struct Point(i32, i32, i32);

let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);

black和origin值的类型不同,因为它们是不同的元组结构体的实例。在其他方面,元组结构体实例类似于元组:可以将其解构为单独的部分,也可以使用.后跟索引来访问单独的值,等等。

类单元结构体

类单元结构体(unit-like struct)是没有任何字段的空struct。

1
struct St;

unit-like struct常用于需要在某个类型上实现trait,但该类型又不需要存储数据的情况。

实现struct的方法

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct Rectangle{
width: u32,
height: u32,
}

impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}

fn perimeter(&self) -> u32 {
(self.width + self.height) * 2
}
}

fn main() {
let rect1 = Rectangle{width: 30, height: 50};
println!("{},{}", rect1.area(), rect1.perimeter());
}

方法定义在impl Struct_Name {}中,可将方法定义在多个impl Struct_Name {}中。如下。实际上,每次impl Struct_Name{}都表示打开struct的空间,这有点类似于Ruby,可随时打开对象的单例类空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}

fn perimeter(&self) -> u32 {
(self.width + self.height) * 2
}
}

impl Rectangle {
fn include(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}

所有struct的实例方法的第一个参数都是self(的不同形式)。self表示调用方法时的Struct实例对象(如rect1.area()时,self就是rect1)。有如下几种self形式:

定义方法时很少使用第一种形式fn f(self),因为这会使得调用方法后对象立即消失。但有时候也能派上场,例如可用于替换对象:调用方法后原对象消失,但返回另一个替换后的对象。

Rust的自动引用和解引用

在C/C++语言中,有两个不同的运算符来调用方法:.直接在对象上调用方法,->在一个对象指针上调用方法,这时需要先解引用(dereference)指针。

换句话说,如果obj是一个指针,那么obj->something()就像(*obj).something()一样。更典型的是Perl,Perl对象总是引用类型,因此它调用方法时总是使用obj->m()形式。

Rust没有与->等效的运算符,但Rust有一个叫自动引用和解引用(automatic referencing and dereferencing)的功能。方法调用是Rust中少数几个拥有这种行为的地方。

它是这样工作的:当使用ob.something()调用方法时,Rust会根据所调用方法的签名进行推断(即根据方法的接收者self参数的形式进行推断),然后自动为object添加&, &mut, *以便使obj与方法签名匹配。

也就是说,下面代码是等价的,但第一行看起来简洁的多:

1
2
p1.distance(&p2); 
(&p1).distance(&p2);

关联函数(associate functions)

关联函数是指第一个参数不是self(的各种形式),但和Struct有关联关系的函数。关联方法类似于其他语言中类方法或静态方法的概念。

调用关联方法的语法Struct::func()。因此,String::from()构造String类型的字符串时,实际上就是在调用String的关联方法from()。

例如,可以定义一个用于构造实例的关联函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct Rectangle {
width: u32,
height: u32,
}

impl Rectangle {
// 关联方法new:构造Rectangle的实例对象
fn new(width: u32, height: u32) -> Rectangle {
Rectangle { width, height }
}
}

impl Rectangle {
fn area(&self) -> u32 { self.width * self.height }
}

fn main() {
// 调用关联方法
let rect1 = Rectangle::new(30, 50);
let rect2 = Rectangle::new(20, 50);
println!("{}", rect1.area());
}