2024 Rust现代实用教程:Ownership与结构体、枚举
文章目录
- 一、Rust的内存管理模型
- 1.GC(Stop the world)
- 2.C/C++内存错误大全
- 3.Rust的内存管理模型
- 二、String与&str
- 1.String与&str如何选择
- 2.Example
- 三、枚举与匹配模式
- 1.常见的枚举类型:Option和Result
- 2.匹配模式
- 四、结构体、方法、关联函数、关联变量
- 五、Ownership与结构体
- 五、堆与栈、Copy与Move
- 1.堆与栈
- 2.Box智能指针
- 3.Copy与Move
- 4.小结
- 参考
一、Rust的内存管理模型
Rust
- 所有权机制
- 引用称之为借用
1.GC(Stop the world)
"Stop the world"是与垃圾回收(Garbage Col lection)相关的术语,它指的是在进行垃圾回收时系统暂停程序的运行。
这个术语主要用于描述一种全局性的暂停,即所有应用线程都被停止,以便垃圾回收器能够安全地进行工作。这种全局性的停止会导致一些潜在的问题,特别是对于需要低延迟和高性能的应用程序。
需要注意的是,并非所有的垃圾回收算法都需要"stop the world’",有一些现代的垃圾回收器采用了一些技术来减小全局停顿的影响,比如并发垃圾回收和增量垃圾回收。
2.C/C++内存错误大全
1.内存泄漏(Memory Leaks)
int*ptr new int;
//忘记释放内存
/delete ptr:
2.悬空指针(Dangl ing Pointers)
int*ptr new int;
delete ptr;//ptr现在是悬空指针
3。重复释放(Double Free)
int*ptr new int;
delete ptr;
delete ptr;
4.数组越界(Array Out of Bounds
int arr [5];
arr[5]=10:
5.野指针(Memory Leaks)
int*ptr;
*ptr=10;/野指针
6.使用已经释放的内存(Use After Free)
int*ptr new int;
delete ptr;
*ptr=10;//使用已释放的内存
·7.堆栈溢出(Stack0 verflow)
递归堆栈溢出
·8.不匹配的new/delete或malloc,free
3.Rust的内存管理模型
所有权系统(Ownership System)
借用(Bor rowing)
- 不可变引用(不可变借用)
- 可变引用(可变借用)
生命周期(Lifetimes)
引数计数(Reference Counting)
eg:
fn get_length(s: String) -> usize {
println!("String: {}", s);
// 函数结束之后 main::s1也销毁了
s.len()
}
fn main() {
// copy move
// copy
let c1 = 1;
let c2 = c1;
println!("{}", c1);
let s1 = String::from("value");
// let s2 = s1;//所有权转移
let s2 = s1.clone(); // 深拷贝
// println!("{s1}"); // value borrowed here after move
println!("{}", s1);
let len = get_length(s1);
println!("{}", len);
let back = first_word("hello world");
println!("{}", back);
let back = first_word("we are the world");
println!("{}", back);
}
fn dangle() -> String {
"hello".to_owned()
}
// 静态的声明周期
// 不建议使用
fn dangle_static() -> &'static str {
"jdkfj"
}
// 传进来是引用,传出去也是引用
// String 与 &str,实际上就是vec u8的ref
fn first_word(s: &str) -> &str {
let bytes: &[u8] = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..] //直接返回s也可以
}
编译以及运行
cargo run
Compiling ch5_ownership_introduce v0.1.0 (/home/wangji/installer/rust/project/ch5_ownership_introduce)
warning: unused variable: `c2`
--> src/main.rs:10:9
|
10 | let c2 = c1;
| ^^ help: if this is intentional, prefix it with an underscore: `_c2`
|
= note: `#[warn(unused_variables)]` on by default
warning: unused variable: `s2`
--> src/main.rs:14:9
|
14 | let s2 = s1.clone(); // 深拷贝
| ^^ help: if this is intentional, prefix it with an underscore: `_s2`
warning: function `dangle` is never used
--> src/main.rs:27:4
|
27 | fn dangle() -> String {
| ^^^^^^
|
= note: `#[warn(dead_code)]` on by default
warning: function `dangle_static` is never used
--> src/main.rs:33:4
|
33 | fn dangle_static() -> &'static str {
| ^^^^^^^^^^^^^
warning: `ch5_ownership_introduce` (bin "ch5_ownership_introduce") generated 4 warnings (run `cargo fix --bin "ch5_ownership_introduce"` to apply 2 suggestions)
Finished dev [unoptimized + debuginfo] target(s) in 0.37s
Running `target/debug/ch5_ownership_introduce`
1
value
String: value
5
hello
we
二、String与&str
String,是一个堆分配的可变字符串类型
- 源码:
pub struct String{
vec:Vec<u8>,
}
- u8的可变数组
&str是指字符串切片引用,是在栈上分配的
- 字符串字面量
- 不可变引用,指向存储在其他地方的UTF-8编码的字符串数据
- 由指针和长度构成(由C++的角度去看)
1.String与&str如何选择
注意String是具有所有权的,而&str并没有
Struct中属性使用String
- 如果不使用显式声明生命周期,则无法使用&str
- 不只是麻烦,还有更多的隐患
函数参数推荐使用&str(如果不想交出所有权)
- &str为参数,可以传递&str和&String
- &String为参数,只能传递&String不能传递&str
2.Example
struct Person<'a> {
name: &'a str, //'a的函数是name的生命周期和Person是一样的
color: String,
age: i32,
}
struct P2Persion {
name: String,
color: String,
age: i32,
}
//===================String 和 &str测试===================
// 可以传递的入参类型:&String, &str
fn print(data: &str) {
println!("{}", data);
}
//===================String 和 &str测试===================
// 可以传递的入参类型是:&String
fn print_string_borrow(data: &String) {
println!("{}", data);
}
fn main() {
//===================String 和 &str测试===================
// 从字面量&str转成String的三种方法:(1)String::from(2)to_string()(3)to_owned()
let name = String::from("Value C++");
let name_2 = "wangji".to_owned();
let course = "Rust".to_string();
// String有很多方法
let new_name = name.replace("C++", "CPP");
println!("{name_2} {name} {course} {new_name}");
//&str 能够直接进行ASICC编码
let rust = "\x52\x75\x73\x74"; // ascii
println!("{rust}");
//===================结构体测试===================
let pp = P2Persion {
name: "value".to_string(),
color: "green".to_string(),
age: 89,
};
//String &str
let color = "green".to_string();
let name = "John";
let people = Person {
name: name,
color: color,
age: 89,
};
// func
let value = "value".to_owned();
print(&value);
print("data");
// print_string_borrow("dd");
print_string_borrow(&value);
}
编译及运行
cargo run
warning: unused variable: `pp`
--> src/main.rs:41:9
|
41 | let pp = P2Persion {
| ^^ help: if this is intentional, prefix it with an underscore: `_pp`
|
= note: `#[warn(unused_variables)]` on by default
warning: unused variable: `people`
--> src/main.rs:50:9
|
50 | let people = Person {
| ^^^^^^ help: if this is intentional, prefix it with an underscore: `_people`
warning: fields `name`, `color`, and `age` are never read
--> src/main.rs:2:5
|
1 | struct Person<'a> {
| ------ fields in this struct
2 | name: &'a str, //'a的函数是name的生命周期和Person是一样的
| ^^^^
3 | color: String,
| ^^^^^
4 | age: i32,
| ^^^
|
= note: `#[warn(dead_code)]` on by default
warning: fields `name`, `color`, and `age` are never read
--> src/main.rs:8:5
|
7 | struct P2Persion {
| --------- fields in this struct
8 | name: String,
| ^^^^
9 | color: String,
| ^^^^^
10 | age: i32,
| ^^^
warning: `ch2_string_str` (bin "ch2_string_str") generated 4 warnings
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/ch2_string_str`
wangji Value C++ Rust Value CPP
Rust
value
data
value
三、枚举与匹配模式
枚举(enums)是一种用户自定义的数据类型,用于表示具有一组离散可能值的变量
- 每种可能值都称为"variant”(变体)
- 枚举名:变体名
枚举的好处
- 可以使你的代码更严谨、更易读
- More robust programs
enum Shape{
Circle(f64)
Rectangle(f64,f64),
Square (f64)
}
1.常见的枚举类型:Option和Result
pub enum Option<T>{
None,
Some (T),
}
pub enum Result<T,E>{
Ok(T)
Err (E),
}
2.匹配模式
1.match关键字实现
2.必须覆盖所有的变体
3.可以用_、,…=、三元(if)等来进行匹配
match number{
0 => printIn!("Zero"),
1|2 => printIn!("One or Two"),
3..=9 => printIn!("From Three to Nine"),
n if n%2 ==0 =>printIn!("Even number"),
_=printIn!("Other"),
}
eg:
use std::collections::btree_set::Union;
enum Color {
Red,
Yellow,
Blue,
}
fn print_color(my_color: Color) {
match my_color {
Color::Red => println!("Red"),
Color::Yellow => println!("Yellow"),
Color::Blue => println!("Blue"),
}
}
enum BuildingLocation {
Number(i32),
Name(String), // 不用&str
Unknown,
}
// rust是面向函数,面向行为
// rust:不能把行为写在struct里面,与C++不同
impl BuildingLocation {
fn print_location(&self) {
match self {
// BuildingLocation::Number(44)
BuildingLocation::Number(c) => println!("building number {}", c),
// BuildingLocation::Name("ok".to_string())
BuildingLocation::Name(s) => println!("building name {}", *s),
BuildingLocation::Unknown => println!("unknown"),
}
}
}
fn main() {
let a = Color::Red;
print_color(a);
// let b = a;所有权已经转移到print_color里面了
let house = BuildingLocation::Name("fdfd".to_string());
house.print_location();
let house = BuildingLocation::Number(1);
house.print_location();
let house = BuildingLocation::Unknown;
house.print_location();
}
编译及运行:
cargo run
Compiling ch7_enum v0.1.0 (/home/wangji/installer/rust/project/ch7_enum)
warning: unused import: `std::collections::btree_set::Union`
--> src/main.rs:1:5
|
1 | use std::collections::btree_set::Union;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
warning: variants `Yellow` and `Blue` are never constructed
--> src/main.rs:5:5
|
3 | enum Color {
| ----- variants in this enum
4 | Red,
5 | Yellow,
| ^^^^^^
6 | Blue,
| ^^^^
|
= note: `#[warn(dead_code)]` on by default
warning: `ch7_enum` (bin "ch7_enum") generated 2 warnings (run `cargo fix --bin "ch7_enum"` to apply 1 suggestion)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 6.46s
Running `target/debug/ch7_enum`
Red
building name fdfd
building number 1
unknown
四、结构体、方法、关联函数、关联变量
结构体间接
- 结构体是一种用户定义的数据类型,用于创建自定义的数据结构。
struct Point{
x:i32,
y:i32,
}
- 每条数据(x和y)称为属性(字段field)
- 通过点(.)来访问结构体中的属性
结构体中的方法
- 在rust中都称之为关联函数,这里为了方便称之为方法了
- 这里的方法是指,通过实例调用(&self、&mut self、self)
- &self。引用
- &mut self,可变self的引用
- self,交出所有权
impl Point
fn distance (&self,other:&Point)->f64{
let dx = (self.x - other.x)as f64;
let dy = (self.y - other.y)as f64;
(dx*dx + dy*dy).sqrt()
}
结构体中的关联函数
- 关联函数是与类型相关的函数,调用时为结构体名::函数名
- Self 就是Point,结构体的名字
impl Point{
fn new(x: u32, y:u32) ->self{
Point{x,y}
}
}
结构体中的关联变量
- 这里的关联变量是指,和结构体类型相关的变量,也可以在特质或是枚举中
impl Point{
const Pl:f64=3.14;
}
- 调用时使用Point::Pl
eg:
enum Flavor {
Spicy,
Sweet,
Fruity,
}
struct Drink {
flavor: Flavor,
price: f64,
}
impl Drink {
// 关联变量
const MAX_PRICE: f64 = 10.0;
// 方法,&self不能修改里面的成员
// Drink::MAX_PRICE,等价写法Self::MAX_PRICE
fn buy(&self) {
if self.price > Drink::MAX_PRICE {
println!("I am poor");
return;
}
println!("buy it");
}
// 关联函数
// Self表示返回Drink结构体
fn new(price: f64) -> Self {
Drink {
flavor: Flavor::Fruity,
price,
}
}
}
fn print_drink(drink: Drink) {
match drink.flavor {
Flavor::Fruity => println!("fruity"),
Flavor::Spicy => println!("spicy"),
Flavor::Sweet => println!("sweet"),
}
println!("{}", drink.price);
}
fn main() {
let sweet = Drink {
flavor: Flavor::Sweet,
price: 6.0,
};
println!("{}", sweet.price);
print_drink(sweet); // sweet 已经被销毁了
// print_drink(sweet);
let sweet = Drink::new(12.0);
sweet.buy();
}
编译及运行:
cargo run
Compiling ch8_struct v0.1.0 (/home/wangji/installer/rust/project/ch8_struct)
warning: variant `Spicy` is never constructed
--> src/main.rs:2:5
|
1 | enum Flavor {
| ------ variant in this enum
2 | Spicy,
| ^^^^^
|
= note: `#[warn(dead_code)]` on by default
warning: `ch8_struct` (bin "ch8_struct") generated 1 warning
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.32s
Running `target/debug/ch8_struct`
6
sweet
6
I am poor
五、Ownership与结构体
Ownership Rules
- 1.Each value in Rust has an owner
- 2.There can only be one owner at a time
- 3.Values are automatically dropped when the owner goes out of scope
Value Passing Semantics值传递语义
每当将值从一个位置传递到另一个位置时,borrow checker:都会重新评估所有权。
- 1.Immutab|e Borrow使用不可变的借用,值的所有权仍归发送方所有,接收方直接接收对该值的引用,而不是该值的副本。但是,他们不能使用该引用来修改它指向的值,编译器不允许这样做。释放资源的责任仍由发送方承担。仅当发件人本身超出范围时,才会删除该值
- 2.Mutable Borrow使用可变的借用所有权和删除值的责任也由发送者承担。但是接收方能够通过他们接收的引用来修改该值。
- 3.Move这是所有权从一个地点转移到另一个地点。borrow checker关于释放该值的决定将由该值的接收者(而不是发送者)通知。由于所有权已从发送方转移到接收方,因此发送方在将引用移动到另一个上下文后不能再使用该引用,发送方在移动后对v|aue的任何使用都会导致错误。
结构体中关联函数的参数
&self
对应的,在函数参数中:(self:&Self)
不可变引用
&mut self
对应的,在函数参数中(self:&mut Self)
可变引用
self
对应的,在函数参数中(self:Self)
Move
impl Point{
get (self:Self)->i32{
self.x
}
}
Example:
struct Counter {
number: i32,
}
impl Counter {
fn new(number: i32) -> Self {
Self { number }
}
// 不可变借用
fn get_number(&self) -> i32 {
self.number
} // Counter::get_number(self: &Self)
// 不可变借用
fn add(&mut self, increment: i32) {
self.number += increment;
} // Counter::add(self: &mut Self, increment:)
// move
fn give_up(self) {
println!("free {}", self.number);
}
/**
等价于下面的写法
fn combine(c1: Self, c2: Self) -> Self {
return Self {
number: c1.number + c2.number,
};
}
*/
fn combine(c1: Self, c2: Self) -> Self {
Self {
number: c1.number + c2.number,
}
}
}
fn main() {
let mut c1 = Counter::new(0);
println!("c1 number {}", c1.get_number());
println!("c1 number {}", c1.get_number());
c1.add(2);
println!("c1 number {}", c1.get_number());
println!("c1 number {}", c1.get_number());
c1.give_up();
// println!("c1 number {}", c1.get_number());
let c1 = Counter::new(2);
let c2 = Counter::new(1);
let c3 = Counter::combine(c1, c2);
// println!("c1 number {}", c1.get_number());
// println!("c2 number {}", c2.get_number());
println!("c3 number {}", c3.get_number());
}
编译及运行:
cargo run
Compiling ch9_ownership_struct v0.1.0 (/home/wangji/installer/rust/project/ch9_ownership_struct)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.31s
Running `target/debug/ch9_ownership_struct`
c1 number 0
c1 number 0
c1 number 2
c1 number 2
free 2
c3 number 3
五、堆与栈、Copy与Move
1.堆与栈
stack
- 1.堆栈将按照获取值的顺序存储值,并以相反的顺序删除值
- 2.操作高效,函数作用域就是在栈上
- 3.堆栈上存储的所有数据都必须具有已知的固定大小数据
heap
- 1.堆的规律性较差,当你把一些东西放到你请求的堆上时,你请求,请求空间,并返回一个指针,这是该位置的地址
- 2.长度不确定
2.Box智能指针
Box是一个智能指针,它提供对堆分配内存的所有权。它允许你将数据存储在堆上而不是栈上,并且在复制或移动时保持对数据的唯一拥有权。使用Bo×可以避免一些内存管理问题,如悬垂指针和重复释放。
- 1.所有权转移
- 2.释放内存
- 3.解引用
- 4.构建递归数据结构
3.Copy与Move
Move:所有权转移
Clone:深拷贝
Copy:Copy是在CIone的基础建立的marker trait(Rust中最类似继承的关系)
- 1.trait(特质)是一种定义共享行为的机制。Clone也是特质
- 2 marker trait(标记特质)是一个没有任何方法的trait,他主要用于向编译期传递某些信息,以改变类型的默认行为
4.小结
stack
- 1.基础类型
- 2.tuple和array
- 3.struct与枚举等也是存储在栈上,如果属性有String等在堆上的数据类型会有指向堆的
heap
- Box Rc String/Vec等
一般来说在栈上的数据类型都默认是copy的,但是struct等默认为move,需要Copy只需要设置数据类型实现Copy特质即可,或是调用CIone函数(需要实现CIone特质)
box Example:
struct Point {
x: i32,
y: i32,
}
fn main() {
let boxed_point = Box::new(Point { x: 10, y: 20 });
println!("x:{}, y:{}", boxed_point.x, boxed_point.y);
let mut boxed_point = Box::new(32);
println!("{}", *boxed_point);
*boxed_point += 10;
println!("{}", *boxed_point);
}
编译及运行:
cargo run
Compiling ch10_box v0.1.0 (/home/wangji/installer/rust/project/ch10_box)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 9.43s
Running `target/debug/ch10_box`
x:10, y:20
32
42
copy and move Example:
#[derive(Debug, Clone, Copy)]
struct Book {
page: i32,
rating: f64,
}
fn main() {
let x = vec![1, 2, 3, 4];
let y = x.clone();
println!("{:#?}", y);
println!("{:?}", x);
let x = "ss".to_string();
let y = x.clone();
println!("{:?}", x);
let b1 = Book {
page: 1,
rating: 0.1,
};
let b2 = b1; // copy
println!("{}", b1.page);
println!("{:?}", b2);
}
编译及运行:
cargo run
warning: unused variable: `y`
--> src/main.rs:14:9
|
14 | let y = x.clone();
| ^ help: if this is intentional, prefix it with an underscore: `_y`
|
= note: `#[warn(unused_variables)]` on by default
warning: field `rating` is never read
--> src/main.rs:4:5
|
2 | struct Book {
| ---- field in this struct
3 | page: i32,
4 | rating: f64,
| ^^^^^^
|
= note: `Book` has derived impls for the traits `Clone` and `Debug`, but these are intentionally ignored during dead code analysis
= note: `#[warn(dead_code)]` on by default
warning: `ch10_copy_move` (bin "ch10_copy_move") generated 2 warnings
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/ch10_copy_move`
[
1,
2,
3,
4,
]
[1, 2, 3, 4]
"ss"
1
Book { page: 1, rating: 0.1 }
参考
- 2024 Rust现代实用教程