Rust的结构体(struct)是一种自定义的数据类型,允许开发者命名和包装多个相关的值,以形成有意义的数据组合。在Rust中结构体不仅用于数据组织,还密切结合了Rust的内存安全性和所有权模型特性,在开发系统编程过程中很有用。
定义和实例化
使用struct关键字来定义一个结构体。可以定义字段的类型,但所有字段都必须在创建实例时进行指定。
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
//创建结构体
//初始化实例时,每个字段都需要进行初始化
//初始化时的字段顺序不需要和结构体定义时的顺序一致
let user1 = User {
email: String::from("[email protected]"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
//通过.操作符即可访问结构体实例内部的字段值
user1.email = String::from("[email protected]");
//通过旧的结构体内容初始化新的结构体
let user2 = User {
active: user1.active,
username: user1.username,
email: String::from("[email protected]"),
sign_in_count: user1.sign_in_count,
};
//缺省的初始化复制
//除了email之外 其它变量都用user1的 username字段发生了所有权转移 作为结果 user1无法再被使用
//把结构体中具有所有权的字段转移出去后,将无法再访问该字段,但是可以正常访问其它的字段
let user2 = User {
email: String::from("[email protected]"),
..user1
};
可变性
在Rust中结构体实例的可变性由整个实例来控制。如果一个实例被声明为可变的,那么所有的字段都是可变的。
//声明可变的结构体
let mut user1 = User {
// 初始化代码相同
};
user1.email = String::from("[email protected]");
构造函数
Rust没有专门的构造函数语法,但可以通过实现关联函数(通常是new)来模拟构造函数行为。
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
impl User {
fn new(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}
}
let user2 = User::new(String::from("[email protected]"), String::from("testuser"));
方法定义
结构体可以包含方法,方法是定义在impl块中的函数,它们可以访问结构体的字段和其他方法。 Rust中的函数分为两类:
1.实例方法(有self参数) 直接作用于结构体的实例,可以访问和修改实例的属性。
2.关联函数(无self参数) 与结构体类型关联,但不作用于具体的实例。它们常用于执行不需要直接访问结构体字段的操作,如构造新实例。
当方法包含self参数时,它们是实例方法。这意味着它们操作的是结构体的一个具体实例,可以访问和修改实例的数据。
self 参数可以以三种形式出现:
- self: 这种方式获取结构体的所有权,通常用于需要消耗结构体实例的场景。
- &self: 这是最常见的形式,代表对结构体实例的不可变引用,用于当方法只需要读取而不修改结构体数据时。
- &mut self: 代表对结构体实例的可变引用,用于需要修改实例数据的方法。
不包含self参数的方法被称为关联函数。这些函数与结构体类型相关联,但不与结构体的某个具体实例相关联。它们类似于其他语言中的静态方法。关联函数通常用于构造器(创建结构体实例的函数)或与结构体逻辑相关但不依赖于具体实例的工具函数。
impl User {
//实例方法
fn email(&self) -> &str {
&self.email
}
//关联函数
fn new(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}
}
println!("User email: {}", user1.email());
特殊结构体
在Rust中,除了常规的命名字段结构体外,还有两种特殊类型的结构体:元组结构体(tuple structs)和单元结构体(unit structs)。这两种结构体提供了更多的灵活性和表达力,以适应不同的编程需求。
元组结构体本质上是命名元组。它们的字段没有名字,只有类型,适用于需要打包几个数据但不需要为每个数据字段命名的场景。元组结构体的语法和元组类似,但是它有一个具体的类型名称。当你需要创建一个结构体,但字段名不重要或者可以省略时,元组结构体是一个不错的选择。它们简化了代码,尤其是在字段名可能会增加语义重复的情况下。
//元组结构体
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
单元结构体没有任何字段,通常用于表示不需要存储数据的类型,但你想利用它的类型安全优势。它们类似于空元组()(也称为单元类型)。单元结构体的一个用途是实现特定的trait而不存储数据。例如,你可能有一个行为像标记的trait,这些trait通过单元结构体来实现,以便能够用类型系统强制某种特定的行为或属性。常用于标记类型,或在泛型编程中作为类型占位符,还可以在实现无状态的trait时使用。
//单元结构体 没有任何属性
struct AlwaysEqual;
let subject = AlwaysEqual;
//不关心AlwaysEqual的字段数据,只关心它的行为
impl SomeTrait for AlwaysEqual {
}
输出结构体
要在Rust中输出结构体的内容,你需要为该结构体实现Debug或Display特性。Debug 特性主要用于调试目的,它会输出结构体的所有字段,而Display特性用于更正式的输出,可以自定义输出内容。
实现Debug特性
#[derive(Debug)]
struct Person {
name: String,
age: u8
}
let person = Person {
name: String::from("Alice"),
age: 30
};
// 使用 {:?} 来格式化输出 Debug 版本
println!("{:?}", person);
实现Display特性
use std::fmt;
struct Person {
name: String,
age: u8
}
impl fmt::Display for Person {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} is {} years old", self.name, self.age)
}
}
let person = Person {
name: String::from("Alice"),
age: 30
};
println!("{}", person); // 使用 {} 来格式化输出 Display 版本
与C++结构体的差异
1.内存安全性
Rust结构体在编译时强制执行所有权和借用规则,从而无需担心空悬指针和内存泄漏。C++则依赖于程序员对指针和内存管理的手动控制。
2.方法和成员函数
C++允许类成员默认可变。如果你需要不可变性,你必须明确地使用const关键词。而在Rust中,不可变性是默认的,可变性必须明确声明。
3.构造函数和析构函数
C++有构造函数和析构函数的概念,用于初始化和清理资源。Rust则使用所有权系统自动处理资源清理,不需要析构函数,虽然可以实现Drop trait来定制清理行为。
4.继承
C++支持类的继承。而Rust不支持传统的面向对象编程中的继承,而是使用特性(traits)和组合来达到类似的功能复用。