结构体是一种自定义数据类型,它允许我们命名多个相关的值并将他们的组成一个有机的结合体。此处可以类比Java的类、Golang的struct和C语言的结构体。
Rust中结构体的定义是使用关键字:struct
。例子:
struct Person {
name: String,
sex: bool,
age: i32,
};
这里和golang和c的很相似,很好理解!
在Rust中实例化一个结构体很简单,直接使用结构体的名字并使用一对大括号包含键值对来创建对象。例子:
let person = Person {
name: String::from("Tom"),
sex: true,
age: 20,
};
实例化之后,我们就可以使用点号访问实例中的特定字段。
println!("{}", person.name); // Tom
上面的person默认是不可变的,如果想要修改person内容可以将person变量修改为mut,如果实例申明为可变的,那么实例中所有的字段都是可变的。
let mut person = Person {
name: String::from("Tom"),
sex: true,
age: 20,
};
println!("{}", person.name); // Tom
person.name = String::from("HelloWorld");
println!("{}", person.name); // HelloWorld
这里通过一个方法对Person进行实例化,例子:
fn builder_person(name: String, age: i32) -> Person {
Person {
name,
sex: true,
age,
}
}
这个例子主要是为了说明一个问题,变量名和字段名相同时可以简化字段初始化!
这里我们在介绍一个Rust的语法糖:
let person = Person {
name: String::from("Tom"),
sex: true,
age: 20,
};
let person2 = Person {
name: String::from("Mark"),
..person
};
这里的双点号表示剩下的那些还没有显示赋值的字段都使用给定实例拥有相同的值!等价于下面的例子:
let person2 = Person {
name: String::from("Mark"),
sex: person.sex,
age: person.age,
};
前面的章节我们一值都是使用println!
这个宏输出数据的,下面我们试一下输出Person
:
println!("{}", person);
此时Rust编译器会给我们如下的错误提示:
error[E0277]: `Person` doesn't implement `std::fmt::Display`
--> src/main.rs:16:20
|
16 | println!("{}", person);
| ^^^^^^ `Person` cannot be formatted with the default formatter
这是因为println!宏可以执行多种不同的文本格式化命令,而作为默认选项,格式化文本的花括号会告知println!
使用名为Display的格式化方式。虽然我们目前接触的基础类型都默认实现了Display,但是对于结构体Rust并没有提供默认的Display实现。此时只需要在我们的结构体上使用#[derive(Debug)]
就可以解决!
#[derive(Debug)]
struct Person {
name: String,
sex: bool,
age: i32,
}
let person = Person {
name: String::from("Tom"),
sex: true,
age: 20,
};
println!("{:?}", person); // Person { name: "Tom", sex: true, age: 20 }
// Person {
// name: "Tom",
// sex: true,
// age: 20,
// }
println!("{:#?}", person);
这里我们还可以实现Display的fmt方法实现自定义的格式化。Debug是Rust为我们实现的一种fmt实现方式。具体的后面章节在介绍
下面我们在介绍一种不同于上面的类似于元组的元组结构体。元组结构体同样拥有用于表明自身含义的名称,但是你无须在申明它时对其字段进行命名,仅保留字段类型即可。例子:
struct Color(i32, i32, i32);
let red = Color(255, 255, 0); // 实例化
元组结构体还支持像元组一样,通过点号索引来访问字段:
let red = Color(255, 255, 0);
let r = red.0;
let g = red.1;
let b = red.2;
println!("r:{}, g:{}, b:{}", r, g, b); // r:255, g:255, b:0
Rust允许我们创建没有任何字段的结构体,这种结构体和空元组十分相似,所有这种类型的结构体被称为空结构体。
方法和函数十分相识,它们都使用关键字fn
已经名称来申明并且都拥有参数和返回值;不同的是方法第一个参数是self,用于指向调用该方法的结构体实例。
下面我们为Person
定义一个say
的方法,定以结构体的方式需要使用关键字impl
和结构体名称组成一个代码块,在里面编写结构体对应的方法:
#[derive(Debug)]
struct Person {
name: String,
sex: bool,
age: i32,
}
impl Person {
// 此处改成self是怎么样的呢?
fn say(&self) {
println!("{}说:hello world", self.name);
}
}
fn main() {
let person = Person {
name: String::from("Tom"),
sex: true,
age: 20,
};
person.say();
}
此时大家肯定会问self
和&self
什么区别?
self
:表示传递的是一个对象,会发生所有权转移,对象的所有权会传递给方法中;&self
:表示传递的是一个引用,不会发生对象所有权转移;接着看一个修改Person的name属性的方法怎么编写,需要在self前添加mut关键字修改,表明可变的:
impl Person {
fn set_name(&mut self, name: String) {
self.name = name;
}
}
在impl块中Rust还允许我们定义不用self作为参数的函数。由于这类函数与结构体相互关联,也被叫做关联函数。这里函数其实用的挺多的注入:String::from()
。例子:
impl Person {
fn new(name: String) -> Self {
Person {
name,
sex: true,
age: 10,
}
}
}
此时我们就可以和String::from()
一样调用了: Person::new(String::from("Tom"))
。
在Rust中每个结构体允许拥有多个impl块。例子:
impl Person {
fn new(name: String) -> Self {
Person {
name,
sex: true,
age: 10,
}
}
fn say(&self) {
println!("{}说:hello world", self.name);
}
}
impl Person {
fn set_name(&mut self, name: String) {
self.name = name;
}
}
下一章见!