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// copylet 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 moveprintln!("{}", 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 runCompiling 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 defaultwarning: 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 defaultwarning: 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.37sRunning `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"; // asciiprintln!("{rust}");//===================结构体测试===================let pp = P2Persion {name: "value".to_string(),color: "green".to_string(),age: 89,};//String &strlet color = "green".to_string();let name = "John";let people = Person {name: name,color: color,age: 89,};// funclet 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 defaultwarning: 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 defaultwarning: 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 warningsFinished `dev` profile [unoptimized + debuginfo] target(s) in 0.00sRunning `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), // 不用&strUnknown,
}// 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 runCompiling 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 defaultwarning: 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 defaultwarning: `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.46sRunning `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_PRICEfn 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 runCompiling 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 defaultwarning: `ch8_struct` (bin "ch8_struct") generated 1 warningFinished `dev` profile [unoptimized + debuginfo] target(s) in 0.32sRunning `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)
Moveimpl 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:)// movefn 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.31sRunning `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 runCompiling ch10_box v0.1.0 (/home/wangji/installer/rust/project/ch10_box)Finished `dev` profile [unoptimized + debuginfo] target(s) in 9.43sRunning `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; // copyprintln!("{}", 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 defaultwarning: 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 defaultwarning: `ch10_copy_move` (bin "ch10_copy_move") generated 2 warningsFinished `dev` profile [unoptimized + debuginfo] target(s) in 0.00sRunning `target/debug/ch10_copy_move`
[1,2,3,4,
]
[1, 2, 3, 4]
"ss"
1
Book { page: 1, rating: 0.1 }
参考
- 2024 Rust现代实用教程