Rust

Preface
正好最近ZR先生暑校在学OOP,就临时抽出几天时间来跟一下。
学一下Rust,后续研究研究Tauri框架
笔记性质,内容来自菜鸟教程(Runoob)
Rust 环境配置
懒了,先用在线环境吧。。。
Cargo 包管理器
说实话学编程语言真的没什么可以记录的
语言基础
println!() 和 print!()。这两个”函数”都是向命令行输出字符串的方法,区别仅在于前者会在输出的最后附加输出一个换行符。
输出类似于Python的format
格式字符串中通过 分别转义代表 { 和 }。但是其他常用转义字符与 C 语言里的转义字符一样,都是反斜杠开头的形式。
Declaring Variables
1 | let x = 5; |
1 | a = "abc"; //错,类型不同 |
1 | let mut x = 5; |
不可变变量不是常量,不可变变量是可以被rebind的,但是不能被改变,也就是重新开辟一个内存空间
而可变变量的再赋值是在原内存空间上进行的
变量的值可以”重新绑定”(常量不可以),但在”重新绑定”以前不能私自被改变,这样可以确保在每一次”绑定”之后的区域里编译器可以充分的推理程序逻辑。 ????????
“重新绑定”被称为”shadowing”,非常类似于Python中的assignment
Types
- Integer
i/u 8/16/32/64/128
arch-dependent isize/usize
f 32/64(default)
支持自运算,不支持++,– - bool
- char
一般推荐使用字符串储存 UTF-8 文字
在 Rust 中字符串和字符都必须使用 UTF-8 编码,否则编译器会报错。 - tup
- 数组,跟Python有一点点类似,但是默认都是不可变的
粘过来一点示例 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19let a = [1, 2, 3, 4, 5];
// a 是一个长度为 5 的整型数组
let b = ["January", "February", "March"];
// b 是一个长度为 3 的字符串数组
let c: [i32; 5] = [1, 2, 3, 4, 5];
// c 是一个长度为 5 的 i32 数组
let d = [3; 5];
// 等同于 let d = [3, 3, 3, 3, 3];
let first = a[0];
let second = a[1];
// 数组访问
a[0] = 123; // 错误:数组 a 不可变
let mut a = [1, 2, 3];
a[0] = 4; // 正确
Annotation
形似C
三个正斜杠 /// 依然是合法的注释开始。所以 Rust 可以用 /// 作为说明文档注释的开头
可以显示在 IDE 中
Cargo 具有 cargo doc 功能,开发者可以通过这个命令将工程中的说明注释转换成 HTML 格式的说明文档。
Function
fn <函数名> ( <参数> name: type ) <函数体>
不要求顺序
1 | { |
块中可以使用函数语句,最后一个步骤是表达式,此表达式的结果值是整个表达式块所代表的值。这种表达式块叫做函数体表达式。
道理有点类似于lambda表达式
返回值必须指明,否则认为是纯过程
1 | fn add(a: i32, b: i32) -> i32 { |
Control Flow
条件语句非常类似于C
两个区别
- 不能用单语句代替块(必须用花括号)
- 条件
可以
不写小括号
条件语句并不是非零即真,而必须是bool!
条件的选项当然也可以是函数体表达式
1 | fn main() { |
为什么if外面不需要花括号????????
循环while类似于C,区别同if/else
for循环类似于Python,还有一种有趣的整形遍历
1 | for i in 0..5 { |
无限循环 loop
还可以有返回值的循环break ...;
这里究竟是函数还是表达式????????
Rust 高级特性
所有权(关于内存分配)
可行域类似。
在可行域结束后Rust自动释放内存。
变量与数据交互
- 移动
- 数据在栈中
基本类型数据,整形,浮点,布尔,字符和只包括前四个的元组。
直接复制数据。 - 数据在堆中
为了方便回收,当一个新指针指向一个堆中的数据时,原来的指针就会被废弃。
赋值给其他变量之后,原来的变量就不能再使用了。
- 数据在栈中
- 克隆
在堆中复制一份绑定上去(指针还是一对一 - 引用
可以用s2 = &s1
来引用s1,相当于是s2指向s1的指针,s1相当于一个proxy
引用只是借用值,不会获得值的所有权。
引用本身也是一个类型(就像是指针类型)
如果租借完之后原来的所有权被转让,那么就无法访问了,除非重新租借
A有一所房子,B可以住,但是A也可以卖掉房子,B就要被赶走了
B不可以改动房子,但如果合同签好了可以改就可以改,s2 = &mut s1
可变引用只能有一个(也不能再有不可变引用),不可变引用可以有多个
可以看出这样避免了数据竞争。 - 有所有权机制的保证,Rust可以在编译时检查出空指针和飘飞的指针,从而在编译阶段避免这些问题。
1 | fn main() { |
如果将变量当作参数传入函数,那么它和移动的效果是一样的。
1 | fn main() { |
被当作函数返回值的变量所有权将会被移动出函数并返回到调用函数的地方,而不会直接被无效释放。
- 引用
当一个变量的值被引用时,变量本身不会被认定无效。
因为”引用”并没有在栈中复制变量的值引用不会获得值的所有权。栗子 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// Code 1
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);// 正确
}
fn calculate_length(s: &String) -> usize {
s.len()
}
// Code 2
fn main() {
let s1 = String::from("hello");
let len = calculate_length(s1);
println!("The length of '{}' is {}.", s1, len);// 错,没有所有权!
}
fn calculate_length(s: String) -> usize {
s.len()
}
引用只能租借(Borrow)值的所有权。引用本身也是一个类型并具有一个值,这个值记录的是别的值所在的位置,但引用不具有所指值的所有权
租借来使用权的变量不能修改值,1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17fn main() {
let s1 = String::from("hello");
let s2 = &s1;
let s3 = s1;
println!("{}", s2);// 错误!所有权发生变更,s2需要重新租借
// 注意,此处的报错一般是所有权被租借后不能转让,这种描述似乎不准确,如下
}
fn main() {
let s1 = String::from("hello");
let mut s2 = &s1;
let s3 = s1;
let s2 = &s3;
println!("{}", s2);
//这段程序是正确的,此处可以看出在所有权租借状况下可以变更,本质还是重新租借的问题。
//注意Rust是编译型语言,不同于解释型语言,它的编译语法检查是上下文敏感的。
//不知道说的对不对。。。
}只有使用权,没有处置权
。
也可以授予处置权:&mut
(后面就不用&了)可变引用不允许多重引用,但不可变引用可以。
】
结构体
所有权问题,结构体必须具备所有字段的所有权,因此一般不能用引用
打印结构体
导入#[derive(Debug)]
宏,然后println!("{:?}", 结构体名);
或者println!("{:#?}", 结构体名);
(更美观)
结构体方法
第一个参数必须是&self
,不需要声明类型
用impl
关键字在结构体外部实现方法
结构体关联函数
类似于Python的类方法,没有self参数
使用的时候结构体名::函数名
好像就是个关联,没有什么特别实际的意义。
文件组织
根是crate
路径是用::
分隔的
文件是模块,模块是树状结构
绝对路径:crate::a::b::c
相对路径:super::a
或者self::a
以模块为单位组织
默认是私有的,可以用pub
关键字公开
模块中的结构体也是私有的,可以用pub
公开
字段也是私有的,可以用pub
公开
use
关键字可以导入模块,as
关键字可以别名pub use
可以将模块公开?
枚举
类似于C的枚举
1 | enum IpAddrKind { |
可以带有数据
1 | enum IpAddr { |
也可以是不同类型
1 | enum IpAddr { |
甚至可以含有结构体
1 | enum Message { |
match
类似于switch
1 | fn route(ip_type: IpAddrKind) { |
可以带有值
1 | fn route(ip_type: IpAddr) { |
match
语句也可以当作表达式来返回值,但所有的分支都必须返回相同类型的值
1 | fn route(ip_type: IpAddr) -> String { |
不只是枚举类型,match
也可以用于别的类型
但需要用_
来匹配其他情况
if let
1 | if let 匹配值 = 源变量 { |
类似于两个类的match
语句的简化版
还可以有else
语句
异常处理
panic!
宏可以抛出异常
可恢复异常:Result
枚举类型,Ok
和Err
两种情况
1 | enum Result<T, E> { |
标准库里可以产生异常的函数都返回Result
类型
一个不错的写法
1 | use std::fs::File; |
自己写的函数呢?
1 | fn f(i: i32) -> Result<i32, bool> { |
? 符的实际作用是将 Result 类非异常的值直接取出,如果有异常就将异常 Result 返回出去。所以,? 符仅用于返回值类型为 Result<T, E> 的函数,其中 E 类型必须和 ? 所处理的 Result 的 E 类型一致。
用kind方法可以获取错误类型
像try catch一样的用法
1 |
|
- Post title:Rust
- Post author:Jackcui
- Create time:2023-07-10 19:34:00
- Post link:https://jackcuii.github.io/2023/07/10/Rust/
- Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.