Rust note

news/2024/11/23 2:53:17/

Rust

该文摘抄或总结自《Rust 权威指南》将持续跟进

基础篇

Hello, world!

fn main() {println!("Hello, world!");
}

变量声明

使用let 关键字进行声明:
let 变量名: 类型;
也可以同时赋初始值:
let 变量名: 类型 = 值;
编译器可对部分值的类型进行推导:
let 变量名 = 值;

变量类型——标量类型

1.整型

i8、i16、i32,i64、i128、isize
u8、u16、u32、u64、u128、usize

isize、usize为平台相关类型

let num: i32 = 100;

debug 模式下变量溢出会panic
release 模式下变量溢出会形成环绕,即最大数+1会变成该类型最小的数。若想显示环绕则使用标准库Wrapping类型。

整数字面量示例
Decimal999_999
Hex0xff
Octal0o77
Binary0b1010_0101
Byte (u8 only)b’Z’

默认推导类型:i32

2.浮点型

f32、f64

let num: f32 = 100.00;

浮点数遵循IEEE-754浮点数标准

默认推导类型:f64

3.布尔型

bool
let is_false: bool = false;

4.字符类型

char
let ch: char = 'a';

变量类型——复合类型

两种内置符合类型

1.元组类型

定义:
let tup: (i32, f32, bool) = (1, 2.0, true);
访问:
通过元组名.索引方式访问
tup.0、tup.1、tup.2
解构:
将元组拆分为多个变量
let (a, b, c) = tup;

2.数组类型

定义:
let a = [1, 2, 3, 4, 5, 6];
let a: [i32;6] = [1, 2, 3, 4, 5, 6];
let a: [3;6];等价于let a = [3, 3, 3, 3, 3, 3];
访问:
通过索引访问
a[0]、a[1]

变量的可变性与不可变性

使用以上方式声明的变量均不可修改,对其赋值会提示编译错误,其体现了变量的不可变性。

error[E0384]: cannot assign twice to immutable variable `a`--> src\main.rs:31:5|
30 |     let (a,b,c) = tup;|          -|          ||          first assignment to `a`|          help: consider making this binding mutable: `mut a`
31 |     a = 100;|     ^^^^^^^ cannot assign twice to immutable variable

若想声明可变的变量则在let 后加 mut 关键字(mut: mutable)。
如:let mut a: i32 = 100;此时对a赋值a = 200;是允许的。

变量的隐藏性

同一作用域内可以声明同名变量,下边的变量会隐藏上边的变量。即使类型不同也是可以的。
let a: i32 = 100;
let a: f32 = 100.0;

常量

定义:
const MAX_POINTS: u32 = 100_000;
访问:
可访问,不可修改。

函数

Rust 使用函数使用“蛇形命名”,即小写字母和下划线的组合方式。
无参无返回值形式

fn hello_rust() {println!("hello, rust!")
}

有参有返回值形式

fn add_one(num :i32) -> i32 {return num + 1;
}

函数内部也可以定义函数

fn main() {fn add_one(num :i32) -> i32 {return num + 1;}println!("{}",add_one(1));
}

使用表达式作为函数返回值,不需写return和结尾分号

fn main() {println!("{}",add_one(1)); // 2
}fn add_one(num :i32) -> i32 {num + 1
}

处理多返回值可使用元组

fn f() -> (i32, i64, f32 ) {(1, 2, 3.0)
}

语句

语句以英文半角下的“;”结尾。

表达式

fn main() {let x = 15;let y = {let x = 20 + 1;x + 5};println!("{}",y);
}

x + 6 称之为表达式,x = 后边的20 + 1也是表达式(有点像数学公式) ,表达式一般用一对{}(表示代码块或作用域)括起来,注意表达式一般写在代码块的最后,其后不写分号,写分号就变成了语句。

注释

// 单行注释,可放在语句末尾,分号后/* 多行注释放在单行使用 *//** 多行注释* 多行注释* 多行注释*//// #这是文档注释
/// ## 可以写一些特殊的文本格式,如markdown
/// ```
///  fn f() -> (i32, i64, f32 ) //返回三个值
/// ```
fn f() -> (i32, i64, f32 ) {(1, 2, 3.0)
}

控制流

if else

if

    let a = 3;if a > 2 {println!("a > 2");}

if else if

    let a = 3;if a > 4 {println!("a > 4");}else if a > 3{println!("a > 3");}else {println!("what ?");}

作为表达式使用,表达式中的值必须类型统一。如100、200。不可以为100,“hello”。

    let a = 3;let result = if a > 3 { 100 }else { 200};

循环

rust 支持3种循环loop,for,while
break 用于打破循环,即跳出当前循环
continue 结束当前层循环

loop

    let mut counter = 0;loop {counter += 1;if counter == 100 {println!("循环了100次");break;}}

作为表达式使用

    let mut counter = 0;let result = loop {counter += 1;if counter == 10 {break counter * 2; // 表达式的值}};println!("The result is {}", result);

while

    let mut number = 3;while number != 0 {println!("{}", number);number = number - 1;}

结束循环,跳转到tag处,常用于打破多重循环

    let mut number = 3;'tag: while number != 0 {if number == 1 {break 'tag;}println!("{}", number);number = number - 1;}

for

迭代数组
iter() 为迭代器

    let a = [10, 20, 30, 40, 50];for element in a.iter() {println!("the value is: {}", element);}

正向遍历与反向遍历
1…4 表示范围[1,4),左闭右开,步长为1的从左往右遍历方式
(1…4).rev():反向输出[1,4)的值

    for number in 1..4 {println!("{}", number);}for number in (1..4).rev() {println!("{}", number);}

所有权

Rust使用包含特定规则的所有权系统来管理内存,这套规则允许编译器在编译过程中执行检查工作,而不会产生任何的运行时开销。

所有权规则

Rust中的每一个值都有一个对应的变量作为它的所有者 。
在同一时间内,值有且仅有一个所有者。
当所有者离开自己的作用域时,它持有的值就会被释放掉。

变量作用域

Rust 用“{}”代表作用域。
变量在进入作用域后变得有效。它会保持自己的有效性直到自己离开作用域为止。

{  // 未定义变量slet s = String::from("hello!"); // 变量s生效
} // 变量s离开作用域,Rust自动调用drop函数,销毁变量,归还内存给操作系统

变量和数据交互的方式——移动(所有权转移)

标量类型,因编译时期就可以明确的知道其占用的内存大小,不会涉及堆内存分配,将其完整的放在栈空间即可。所以b = a;复制了a 底层的数据3,两个3都在栈空间,相互独立。

    let mut a = 3;let mut b = a;a += 100;println!("{}",a); // 103println!("{}",b); // 3

变量的移动(所有权转移)
以下代码设计到String的底层结构,String是一个既有栈上内存占用,又有堆上内存占用的数据结构,其发生赋值操作s2 = s1时,会将s1的栈上内容复制一份,给s2,同时将堆上内存的指针转移给s2。s2的出现代替了s1的存在,s1在发生赋值后,便不再可用。这样设计巧妙的避开了同一个内存区域被两次drop。
这其实就是一种浅拷贝操作,但会弃用被拷贝的变量。Rust称上述动作为移动(move)。Rust永远不会自动地创建数据的深度拷贝。因此在Rust中,任何自动的赋值操作都可以被视为高效的。

    let s1 = String::from("hello");let s2 = s1;println!("{}",s1);println!("{}",s2);
error[E0382]: borrow of moved value: `s1`--> src\main.rs:31:19|
28 |     let s1 = String::from("hello");|         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
29 |     let s2 = s1;|              -- value moved here
30 |
31 |     println!("{}",s1);|                   ^^ value borrowed here after move|= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

变量和数据交互的方式——克隆

深度拷贝,调用其clone()方法

   let s1 = String::from("hello");let s2 = s1.clone();println!("s1 = {}, s2 = {}", s1, s2);

栈上数据赋值——实现Copy trait

trait 类似于其他语言的接口,描述了一系列行为。
Copy trait 负责栈上数据的拷贝工作,实现该trait即可将值复制给其他变量,原变量仍可用。
Drop trait 负责堆内存的释放工作。
同一种类型对于 Copy 和 Drop 只能实现其一。
内置的标量类型整型、浮点型、布尔型、字符型数据都实现了Copy trait。若元组中的所有数据类型都实现了Copy trait,那么该元组也具有被Copy的能力。
完整的存储在栈上的类型都可以实现Copy,只要有堆内存和引用其他资源的就不可以实现Copy,否则编译时报错。
用Copy 或 clone的方式对只保留在栈上的类型的数据进行复制都是可以的。

所有权与函数,参数、返回值、作用域

当函数的参数类型实现了Copy,那么传给函数参数的变量,在调用函数后,仍可使用。反之,则发生了所有权转移,参数的作用域即函数的作用域。在函数执行结束后,会自动调用drop,实现回收。所以,在函数调用处之后,是不可使用因调用函数而发生了所有权转移的变量的。若想继续使用该变量,可将其作为函数的返回值,返回后重新使用。引用可以解决这个壁垒。

引用

Rust 使用 & 代表引用语义,使用 * 代表解引用语义。
引用不持有所有权。

不可变引用——借用

    let s1 = String::from("hello");let s2 = &s1;println!("{}",s1);println!("{}",s2);

上述代码s2持有了s1的引用。
引用类型作为函数的参数

fn length(text: &String) -> usize {text.len()
}

调用上述length函数,不会发生所有权转移,在函数退出时,不销毁传入的参数text。
不可变的引用也称之为借用(borrow)。对length函数中的text参数进行修改是不被允许的。

可变引用

作为函数参数,表示为s: &mut String

fn change_string(s: &mut String) {s.push_str("_修改后的.");
}
    let mut s1 = String::from("hello");change_string(&mut s1);println!("{}",s1);

以下代码比较难以理解。在使用可变引用时比较迷惑人。
同一时刻,一个变量只能有一个可变引
如下操作不被允许

    let mut s1 = String::from("hello");let mut s2 = &mut s1; //------s1.push_str("_1"); //        | 可变引用s2,被操作时,不能交叉操作s1s2.push_str("_2"); //--------
error[E0499]: cannot borrow `s1` as mutable more than once at a time--> src\main.rs:38:5|
37 |     let mut s2 = &mut s1;|                  ------- first mutable borrow occurs here
38 |     s1.push_str("_1");|     ^^^^^^^^^^^^^^^^^ second mutable borrow occurs here
39 |     s2.push_str("_2");|     ----------------- first borrow later used here

如下操作同样不被允许

	let mut s1 = String::from("hello");let mut s2 = &mut s1;let mut s3 = &mut s1;s2.push_str("hh");
error[E0499]: cannot borrow `s1` as mutable more than once at a time--> src\main.rs:38:18|
37 |     let mut s2 = &mut s1;|                  ------- first mutable borrow occurs here
38 |     let mut s3 = &mut s1;|                  ^^^^^^^ second mutable borrow occurs here
39 |     s2.push_str("hh");|     ----------------- first borrow later used here

如下操作被允许

    let mut s1 = String::from("hello");let mut s2 = &mut s1;s2.push_str("_2");s1.push_str("_1");

使用作用域隔离可变引用

    let mut s1 = String::from("hello");{let mut s2 = &mut s1;s2.push_str("hh");}let mut s3 = &mut s1;println!("{}",s3);println!("{}",s1);

发生数据竞争的条件:
1.两个或两个以上的指针同时访问同一空间。
2.其中至少有一个指针会向空间中写入数据。
3.没有同步数据访问的机制。

操作可变引用后,不可以操作原来的不可变引用
允许的

    let mut s1 = String::from("hello");let r1 = &s1;let r2 = &s1;println!("{}-{}",r1,r2);let r3 = &mut s1;println!("{}",r3);

不允许的

    let mut s1 = String::from("hello");let r1 = &s1;let r2 = &s1;println!("{}-{}",r1,r2);let r3 = &mut s1;println!("{}",r3);println!("{}",r1);
error[E0502]: cannot borrow `s1` as mutable because it is also borrowed as immutable--> src\main.rs:40:14|
37 |     let r1 = &s1;|              --- immutable borrow occurs here
...
40 |     let r3 = &mut s1;|              ^^^^^^^ mutable borrow occurs here
41 |     println!("{}",r3);
42 |     println!("{}",r1);|                   -- immutable borrow later used here

悬垂引用

未引入生命周期,悬垂引用不被允许

fn main() {let reference_to_nothing = dangle();
}fn dangle() -> &String {let s = String::from("hello");&s
}
error[E0106]: missing lifetime specifier--> src\main.rs:39:16|
39 | fn dangle() -> &String {|                ^ expected named lifetime parameter|= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime|
39 | fn dangle() -> &'static String {|                 +++++++

引用规则

在任何一段给定的时间里,要么只能拥有一个可变引用,要么只能拥有任意数量的不可变引用,引用总是有效的。

切片

切片是一种特殊的引用形式。其不持有所有权。
切片允许我们引用集合中某一段连续的元素序列。
将整个集合转换成切片
let slice = 集合[..];
将集合一部分转换成切片
let slice = 集合[1..];// 从索引1到集合最后
let slice = 集合[1..2];// 从索引1到索引2
let slice = 集合[..2];// 从索引0到索引2
let slice = 集合[0..3];// 从索引0到索引3
切片中[start…end],start与end构成的是一个左闭右开的区间[start,end)

字符串切片

    let s = String::from("hello world");let hello = &s[0..5];let world = &s[6..11];println!("{}",hello);println!("{}",world);

对字符串切片时需考虑字符边界问题,UTF-8编码使用1-4字节表示一个字符。

字符串字面量就是切片。

   let s = "hello, world";let s :&str = "hello world";

str类型即字符串切片类型。

结构体

定义方式

struct 结构体名 {字段名0:类型,字段名1:类型,
}

访问方式

对象.字段名

定义User结构体

struct User {name: String,age: i32,
}

使用User结构体

    let user= User{name: "ophelia".to_string(),age:13,};println!("{:?}",user.name);println!("{:?}",user.age);

用函数方式创建User对象

fn new_user(name: String, age: i32) -> User {User{name:name,age: age,}
}

若函数形参名与结构体中字段重合,则不用写结构体的字段名,直接写入形参

fn new_user_two(name: String, age: i32) -> User {User{name,age,}
}
    let mut user = new_user(String::from("张三"),20);user.age = 22;println!("{}",user.age);let user = new_user_two(String::from("李四"),20);println!("{}",user.name);

使用已有的结构体中的字段
方式1

    let user = new_user_two(String::from("李四"),20);//使用其他user中的字段创建userlet user1 = User{name:String::from("judy"),age:user.age,};

方式2 …运算符。语法糖。

    let user = new_user_two(String::from("李四"),20);//使用其他user中的字段创建userlet user1 = User{name:String::from("julia"),..user};

元组结构体

定义方式

struct 结构体名(类型1,类型2,类型3);

访问方式

对象.索引

定义元组结构体

    struct Color(i32, i32, i32);struct Point(i32, i32, i32);let black = Color(0, 0, 0);let origin = Point(0, 0, 0);println!("{}",black.0);

空结构体

定义方式

struct 结构体名 {}

空结构体一般用作实现trait。实现trait时,不需要使用结构体中的数据。
空结构体与空元组类似 let tup = ();

结构体数据的所有权

若结构体中所有字段都是非引用类型,那么只要结构体携带的数据时有效的,那么结构体就是有效的若结构体中有字段为引用类型,则需要引入生命周期的概念。

为结构体添加派生宏,Debug,实现打印功能

不在结构体上添加 #[derive(Debug)]是无法实现打印功能的。
还需要修改打印宏的占位符的参数 println!("{:?}",user); 加上:?

#[derive(Debug)]
struct User {name: String,age: i32,
}let user= User{name: "ophelia".to_string(),age:13,
};println!("{:?}",user); // User { name: "ophelia", age: 13 }

方法

方法与函数类似:都使用fn关键字及一个名称来进行声明;它们都可以拥有参数和返回值;另外,它们都包含了一段在调用时执行的代码。但是,方法与函数依然是两个不同的概念,因为方法总是被定义在某个结构体(或者枚举类型、trait对象)的上下文中,并且它们的第一个参数永远都是self,用于指代调用该方法的结构体实例。

定义方式

impl 结构体名 {fn 方法名0(self,参数1:类型) -> 返回值类型 {返回值}fn 方法名1(&self,参数1:类型,参数2:类型) -> 返回值类型 {返回值}fn 方法名2(&mut self,参数1:类型) -> 返回值类型 {返回值}fn 函数名(参数1:类型)-> 返回值类型 {返回值}
}

代码

#[derive(Debug)]
struct User {name: String,age: i32,
}impl User {fn say(&self) {println!("say");}fn println(&mut self) {self.name = String::from("张三");println!("{:?}",self);}fn new_user(name:String, age:i32) ->User { // 关联函数,第一个参数非self,常用于结构体的构造器User{name,age,}}
}fn main() {let mut user =  User{name:String::from("judy"),age: 16};let mut user = User::new_user(String::from("judy"),15);user.say();user.println();
}

关联函数

为结构体绑定函数,但是函数参数不传入self。则称之为关联函数,关联函数常用于创建对象。

访问方式一般为: let 变量名 = 结构体::关联函数名(参数);

impl User {fn say(&self) {println!("say");}fn println(&mut self) {self.name = String::from("张三");println!("{:?}",self);}fn new_user(name:String, age:i32) ->User { // 关联函数,第一个参数非self,常用于结构体的构造器User{name,age,}}
}

多impl块

Rust 支持将实现同一类型的方法,拆分到多个impl块中

impl User {fn get_name(self) ->String {self.name}
}
impl User {fn get_age(self) ->i32 {self.age}
}

http://www.ppmy.cn/news/19531.html

相关文章

Service Mesh

Service Mesh 参考如下文章:可以将此文章看作为下面文章的结合: https://zhuanlan.zhihu.com/p/61901608 https://philcalcado.com/2017/08/03/pattern_service_mesh.html https://zhuanlan.zhihu.com/p/153105848?from_voters_pagetrue 微服务演化进…

【GIS】高分辨率遥感影像智能解译

1 绪论 随着航空科技工业的不断成熟与发展,我国遥感卫星研制能力不断攀升,发射数量逐年提高,在轨运行的遥感卫星为社会生产及居民日常生活提供了巨大的支持与便利。我国目前同时在轨运行的遥感卫星数量已超过60颗,每天获取并传回…

恶意代码分析实战 11 恶意代码的网络特征

11.1 Lab14-01 问题 恶意代码使用了哪些网络库?它们的优势是什么? 使用WireShark进行动态分析。 使用另外的机器进行分析对比可知,User-Agent不是硬编码。 请求的URL值得注意。 回答:使用了URLDownloadToCacheFileA函数&#…

Leetcode:46. 全排列、47. 全排列 II(C++)

目录 46. 全排列 问题描述: 实现代码与解析: 回溯: 原理思路: 47. 全排列 II 题目描述: 实现代码与解析: 回溯: 原理思路: 46. 全排列 问题描述: 给定一个不含…

Linux|奇怪的知识---CPU温度监控

前言: 最近我的台式机电脑CPU风扇由于积灰严重,噪音比较大,因此更换了CPU风扇。 更换比较简单没什么好说的,但我想清楚的知道我的CPU温度到底是多少,进而知道这个新风扇是否能给CPU一个清凉的环境,因此需…

SaaS是什么,目前主流的国内SAAS平台提供商有哪些?

SaaS是什么,目前主流的国内SAAS平台提供商有哪些?SaaS这个概念近两年可谓说是十分火热,尤其是后疫情时代。 但还是有很多人对SaaS这个名词云里雾里,被碎片化的信息裹挟,并没有真正意义上理解SaaS的概念。 这篇就综合…

数学小抄: 李群李代数再回顾 [SLAM十四讲]

前言 最近阅读了高翔老师的视觉SLAM十四讲, 也算是了解了当下机器人领域最炙热的机器人领域前沿研究问题大概是怎么样的数学问题. 不得不说视频与书籍真的是深入浅出. 所有第三库无痛安装教程: 知乎链接 SLAM 主要解决的偏颇地说就是在求解一个非线性系统状态方程, 相应的后端…

Element-UI的dialog对话组件内的tinymce弹窗被遮挡的解决办法及其它相关注意事项

问题一&#xff1a;tinymce的弹窗被遮挡 问题截图 解决办法 修改层级 注意要写在 <style></style> 中&#xff0c;我当时没注意&#xff0c;写在了 <style scoped></style> 中&#xff0c;死活没反应。 <style> /* 在el-dialog中tinymce z-ind…