Rust中的自定义类型
希望能帮助你对Rust的自定义类型变量有更好的认识和理解,如有错误,恳请斧正。
1.具名结构体
struct Person {
name: String,
age: u8,
}
fn describe(person: &Person) {
println!("{} is {} years old", person.name, person.age);
}
fn main() {
let mut peter = Person { name: String::from("Peter"), age: 27 };
describe(&peter);
peter.age = 28;
describe(&peter);
let name = String::from("Avery");
let age = 39;
let avery = Person { name, age };
describe(&avery);
let jackie = Person { name: String::from("Jackie"), ..avery };
describe(&jackie);
}
结构体变量的生命周期一定要长于其成员的生命周期,
在声明结构体的时候加上
mut
关键字,允许我们对结构体内容进行修改
如果在结尾加上这些代码就会出现这样的问题:
let ownership_move = peter.name;
describe(&peter)
error[E0382]: borrow of partially moved value: `peter`
--> src/main.rs:26:14
|
25 | let ownership_move = peter.name;
| ---------- value partially moved here
26 | describe(&peter)
| ^^^^^^ value borrowed here after partial move
|
= note: partial move occurs because `peter.name` has type `String`, which does not implement the `Copy` trait
在 Rust 中,String
是一个可变字符串,它在赋值给 ownership_move
时会发生所有权转移,意味着 peter.name
已经不再是有效的 String
实例。
因此,在 describe
函数内部尝试访问 &peter.name
时,由于 peter
的 name
已经被移动了所有权,所以会导致编译错误。
解决方案很简单,时刻注意不要移动所有权
即可
其中..avery
(本例的用法,avery是你想要复制的旧结构体)语法允许我们,轻松的copy
旧结构体的大部分字段,而无需明确键入所有字段,但它必须始终是最后一个元素!
在使用这个语法的时候要注意字段是否实现了
Copy
trait,否则可能会发生所有权转移,出现先前我们看到的编译错误。
#[derive(Debug)]
struct Person {
name: String,
age: u8,
}
fn describe(person: &Person) {
println!("{} is {} years old", person.name, person.age);
}
fn main() {
let mut peter = Person {
name: String::from("Peter"),
age: 27,
};
describe(&peter);
peter.age = 28;
describe(&peter);
let name = String::from("Avery");
let age = 39;
let avery = Person { name, age };
describe(&avery);
let demo_1 = Person {
name: String::from("demo1"),
..avery
};
println!("{:#?}",demo_1);
println!("{:#?}",avery);
let demo_2 = Person{
age:5,
..avery
};
println!("{:#?}",demo_2);
// 加入这一行代码会出现错误:error[E0382]: borrow of partially moved value: `avery`
// println!("{:#?}",avery);
}
2.元组结构体
某些情况下字段的名字并不重要,可以使用元组结构体。
#[derive(Debug)]
struct demo(i32, i32);
#[derive(Debug)]
struct demo_1(String, i32);
fn main() {
let p = demo(10, 6);
println!("{p:#?}");
let p = demo_1(String::from("demo_1"), 3);
println!("{p:#?}");
println!("{} {}", p.0, p.1);
let demo_1(x, y) = p;
println!("{} {}",x,y)
}
元组结构体的常见用法
-
简单的数据封装:可以组合几个相关的值,并且他们之间的关系相对简单,不需要给每个字段赋予特定的含义时。
struct Rgb(u8,u8,u8); // let red = Color(255,0,0); // 上面这段代码表示颜色红色
-
命名元组:元组结构体提供了比普通元组更好的可读性,他为整个组合赋予了明确的意义,即使各个字段没有名字
-
泛型约束和类型系统
-
协议和数据交换
3.枚举
Rust中的枚举是一种强大的类型,它可以让你定义一个类型,该类型能够取有限
的一系列离散的值。
枚举中的每一个变体(Variant)都代表一种可能的状态或值。变体是可以是空的
(unit-like),也可以携带数据
#[derive(Debug)]
enum Direction {
Left,
Right,
}
#[derive(Debug)]
enum PlayerMove {
// 不携带数据,仅作为标记
Pass, // Simple variant
// 携带一组匿名数据,就像元组一样
Run(Direction), // Tuple variant
// 携带命名字段的数据,就像结构体一样
Teleport { x: u32, y: u32 }, // Struct variant
}
fn main() {
let m: PlayerMove = PlayerMove::Run(Direction::Left);
println!("On this turn: {:?}", m);
// 通过模式匹配提取枚举中的值
let pattern_demo = PlayerMove::Teleport {x:10,y:6};
if let PlayerMove::Teleport { x, y } = pattern_demo{
println!("x is {},y is {}",x,y);
}
}
在内存中PlauerMove的实例将会分为两部分存储:
- Discriminant(标签):存储一个整数值,代表当前实例是属于PlayerMove枚举中的哪一个变体。这个整数值具体大小取决于枚举中变体的数量和编译器的优化策略。Rust 编译器会自动为每个变体分配一个唯一的标签值,从 0 开始依次排列(如果没有手动指定)。
- Payload(负载数据):根据变体的定义,存储相应的负载数据。
- 每当创建一个PlayerMove实例,Rust首先在内存中放置标签,接着紧跟负载数据(如果有的话)。
- 在访问PlayerMove的值时,可以通过模式匹配等方式来解析出具体的变体及其携带的数据。
- 可以利用
repr
来控制discriminant,但正常情况下,通常无需关注枚举的底层标签值,以免引起一些莫名其妙的错误。