Zj_W1nd's BLOG

Rust?Rust!(1)

2023/12/14

有空就随便学学,毕竟学技术和语言这东西是需求推动的。现在没有开发的任务和需求学起来就没什么动力。Rust在安全和性能上的宣传吸引了我,所以就随便看看吧,说不定到时候有开发需求了就能用到。不会更新的很快,而且可能也没什么内容,自己能写出来东西是最重要的。

Variable 变量与常量

Constant or Immutable ?

Rust用mut关键字来定义可修改变量,默认变量是不可以在后续修改的。而这个“不可变变量”和“常量”虽然对开发者在结果层面似乎没区别,但是从定义形式和某些功能差别上可以推测出他们使用的截然不同的底层实现方式。参考下面这些规则:

  • 只允许大写字母(类似C语言宏define的通俗约定,define在预处理就会展开替换),类似的思想吧

  • 不允许类型推断(定义强制声明类型)

  • 作用域无关

  • 无法绑定至运行时可得的值可以推断出常量应当是在编译的时候写成rodata的数据。rust从开发层面区分了bss和data这种编译后的区别。

Shadow or Mutable?

同名的变量可以用let二次声明,可以复用同一个名字。编译器の强大。
这玩意和可变变量的二次赋值也不同,允许声明新的类型,而且可以为immutable的变量进行新的定义。是用同一个名字对先前声明的变量的一种“覆盖”(shadow)。看起来就好用的一个特性。

数据类型(基本量)

Rust是静态语言,且是强类型语言(和C一样)。所以编译前需要知道所有的数据类型。例如整数有i32,i64,u32等等,编译器没法推断类型的时候就会直接报错,安全的一。

例如parse()将字符串转成数字这种取决于用户输入的内容方法

整数:默认i32

简单直接的整数类型命名:i32, u64等

Length Signed Unsigned
8bit u8 i8
arch isize usize

存储很通用,也是补码这种。
rust允许数字中加入下划线增强可读性,而且支持类型后缀如65535u16
允许2-16进制的各种输出(decimal-binary),还有8bit_only的byte。计算同理,+ - * / %一样的。

Integer Overflow

debug模式下,rust会检查整数溢出,程序会panic。而发布模式下rust不会检查整数溢出。如果整数溢出发生,则会采用经典的环绕操作。即256–>0

浮点:默认f64

f32和f64,对应float和double。f64是默认的。

Bool类型:True/False

字符类型:32bit, Unicode

utf32,支持emoji和各种语言的输入。

数据类型(复合类型):tuple和array

支持将多个不同类型的值放在一起。

tuple元组:参考python

  • 长度不可变,支持多个类型复合,元素类型不必相同。

  • 可以使用模式匹配来解构一个元组。

  • 用.取元组中的元素看个例子:

1
2
3
4
let tup: (i32,f64,u8) = (500, 6.4, 1);
let (x,y,z) = tup;//模式匹配
//x=500,y=6.4,z=1
//取元素:x=tup.0,y=tup.1,z=tup.2

array数组:没啥特别的

和tuple区别是元素类型必须相同。和C基本一样,数据在栈上连续存放。访问方法同理。后面还有Vector这种和C++类似的更强的数据类型。

1
2
3
let arr:[i32; 5]=[1,2,3,4,5];//数组[元素类型;长度]
let arr:[3;5];//arr=[3,3,3,3,3]
let a=arr[0];

Index Out of Bound?

首先运行会panic,并且直接报错中止。rust不会允许继续对内存访问。
在简单的情况下编译报错,会直接报index out of bounds而拒绝通过编译。而如果数组index的计算较为复杂或者涉及数据结构的嵌套,那编译会通过,但运行同样也会panic。

函数

函数声明:

以fn声明,命名要求字母小写,单词以下划线分开(snake case命名规范)。rust不关心函数声明的位置,即使调用者在被调函数之前声明也无所谓(好!),只要够得着就行。下面的例子是合法的:

1
2
3
4
5
6
fn main(){
callee();
}
fn callee(){
10
}

函数参数:

parameter:形参 arguments:实参
必须在声明中显式指明参数类型。

1
2
3
fn func(x: i32, y: i32){
println!("xxx");
}

函数语句和返回值:注意这里

rust基于语句,语句没有返回值(≠表达式)。函数声明和函数体都是语句,没有返回值。和C这种 x=y=1 不同,rust不允许这种写法。调用是表达式,块也是表达式,宏如println!也是表达式。
C中例如x=y也是表达式(返回左值),但rust不允许其实语句也有返回值,是一个空tuple()

1
2
3
4
5
6
7
8
9
10
11
12
fn main()
{
let x={
let x=1;
x+3 //没加分号,这玩意是个表达式而非语句
};

let x={
let x=1;
x+3; //加分号,这是个语句
};//x=()
}

rust里面,函数返回值用->声明,返回值不可命名。没有显式声明的话,函数返回值是最后一个表达式的值。显式声明或提前返回要用return关键字(但rust中这是不常见的)。

1
2
3
fn five()->i32{
5
}//five()==5

另外注释和C是一样的。

控制结构:

if-else

if的基本要求

if的条件必须要求是bool类型,rust不会将非bool类型转化为bool类型。
与条件相关联的代码称为arm(分支)。条件不用加括号。
rust支持else if condition的写法,顺序判断。参考下面的例子:

1
2
3
4
5
6
7
if number % 4 == 0{
println!("1");
}else if number % 3 == 0{
println!("2");
}else {
println!("123");
}

除了是控制语句以外…

if是一个表达式,其返回值等于其执行的arm块的返回值。也就是说,if-else结构可以作为表达式将值赋给变量。但是作为一门强类型语言,rust的编译器需要在编译前知道所有变量的类型。如果将if-else表达式赋给变量,就必须要求所有arm的返回值类型一样。下面这就是不通过检查的代码:

1
2
let x = if true {5} else {"6"};//wrong!
let x = if true {5} else {6};//right,and x will be 5i32

类似于将C的三目运算符? :和if-else合并了,好!不用面对那些抽象的三目运算符宏定义了(或许?)。

强大的match

match允许一个值与一系列模式(值,变量名,通配符···)进行匹配,然后执行匹配的模式对应的代码。优化版switch。
match也是个表达式,其值为匹配成功所对应arm块的表达式
match必须穷举所有可能否则编译不通过,缺省用_通配符表达

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
enum Coin{
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cnets(coin:Coin)->u8{
match coin{
Coin::Peny => {
println!("code!");
1
},//顺序匹配,这里用{}是举例,里面可以写很多
Coin::Nickel => 5,
Coin::Dime =>10,
Coin::Quarter => 25,
}
}

绑定值

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
enum UsState{
Test,
Test2,
}
enum Coin{
Penny,
Nickle,
Dime,
Quarter(UsState),//绑定一个类型
}
//例子:
match coin{
Coin::Peny => {
println!("code!");
1
},//顺序匹配,这里用{}是举例,里面可以写很多
Coin::Nickel => 5,
Coin::Dime =>10,
Coin::Quarter(state)/*临时指名*/ => {
println!("{}",state);
25
}
}

和option搭配的使用(处理空值的逻辑):

1
2
3
4
match x{//用None和Some写分支
None => None,
Some(i) => Some(i+1),
}

if-let简单控制

if-let只关心一种情况,放弃穷举。

1
2
3
4
5
6
if let Some(3) = x {//if let 值 = 变量
println!("if-let!");
}
else{//可以不用else,更简洁
println!("233");
}

循环

loop:服务器

loop{}持续执行代码块中的内容直至显式指明停止,即使用break
首先,这也是个表达式,和if类似,有返回值。其次,break后面可以加一个表达式来指明loop的返回值。参考下面的例子:

1
2
3
4
5
6
let result = loop{
cnt += 1;
if cnt == 10{
break cnt*2;
}
}

while

和C一样,每次循环都判断一次条件。while condition。性能低一点点。

for: python,好用

用于遍历集合。loop和while也可以但是就得和C写的一样。for取了python的优点,可以以一种迭代器的形式遍历可迭代对象。不会oob,安全简洁。同时,element是一个&i32的引用类型,也就是说for迭代访问的直接就是数组元素本身,没有额外的空间开销。
也和python一样,常规执行用range方法取次数,不包含结尾,用rev方法反转。

1
2
3
4
5
6
7
8
9
fn main(){
let a=[10,20,30,40,50];
for element in a.iter(){//iter()方法返回一个可迭代对象
println!("{}",element);
}
for cnt in (1..4).rev(){//(1..4)就是1-3
println!("{}",cnt);
}
}

结构体struct

struct类型

没啥说的,全看下面的例子就行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
struct User{
username: String,
email: String,
sing_in_account: u64,
active: bool,//最后一项也加逗号
}

let mut user1=User{//设为可变,则所有字段都可变
email: String::from("123@123.com"),
username: String::from("11"),
active: true,//不用顺序赋值
sign_in_account: 123,
//不允许缺少字段,必须赋值4个字段
}

user1.email=String::from("234@234.com");

fn ret(email: String, username: String)->User{
User{
email,//简写,直接用参数作为字段值
username,
active:true,
sign_in_count:1,
}
}
//更新语法:
let user2=User{
email:String::from("123"),
username:String::from("123"),
..user1 //剩余字段直接和user1取一样的
}

Tuple Struct:
用struct关键字,但是更像一个tuple,整体有名字但是没有字段名,可以用模式匹配,.运算符访问成员等等。

看例子:

1
2
struct Color(i32,i32,i32);
let black=Color(0,0,0);

Unit-Like Struct:空结构
所有权:
不包含引用的话,struct实例拥有其所有字段的所有权,实例有效则字段内容都有效。如果放引用要引入生命周期的内容。

一些其他内容: println!()宏中的{}占位符是指示程序调用std::fmt::Display这个trait进行输出。struct默认没有实现这个方法,没法直接打印。但是可以使用调试模式强行输出结构体内容,即在结构体定义开头添加#[derive(Debug)](自定义类型派生调试模式),然后在println!中使用{:?}占位符打印或使用{:#?}占位符格式化打印结构体内容。这是使用的std::fmt::Debug进行输出。

struct方法

类似于类方法,和函数类似,只是说要在struct(enum/trait对象等)上下文中进行定义,第一个参数总是self。用impl关键字声明块然后在其中进行定义(多个impl块是被允许的)。rust会在调用方法时自动进行引用或解引用(不用手动看*和&)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#[Derive(Debug)]
struct Rectangle{
width: u32,
length: u32,
}

impl Rectangle{//
fn area(&self)->u32{//不可变借用,也可以获得所有权或者可变借用,和函数类似
self.width * self.length
}

fn square(size: u32)-> Rectangle{//关联函数(构造器)
Rectangle{
width: size,
length: size,
}
}
}

fn main(){
let rect=Rectangel{
width: 30,
length: 50,
};//记得分号
println!("{}", rect.area());
println!("{:#?}",rect);//以源代码风格输出结构体信息
}

impl块中定义的那些不以self作为第一个参数的函数叫做关联函数(多用于构造器)

枚举

枚举定义和使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum IpAddrKind {//自定义数据类型
V4,//枚举的变体
V6,
}
let v4=IpAddrKind::V4;//标识符命名空间下
let v6=IpAddrKind::V6;
//允许:
enum IpAddr{
V4(u8,u8,u8,u8),//枚举变体可以嵌入任何类型,匿名结构体也行
V6(String),
}
//枚举方法:
impl IpAddrKind{
fn func(&self){}
}

Option枚举

rust没有实际操作意义上的NULL值,但提供了表达类似概念的一个枚举类型:Option<T>,即“可能为空”。定义如下:

1
2
3
4
5
6
7
enum Option<T>{//泛型
Some(T),
None,
}//包含于prelude模块
let x:i8 = 5;
let y: Option<i8>=Some(5);
let z: Option<i8>=None;

y和x是不同类型(i8和Option<i8>),这种设计是为了避免将某些不可预知的NULL值加入操作引起错误的问题(比如字符串连接),Option类的值需要转换类型才能和原本类型一起操作。

CATALOG
  1. 1. Variable 变量与常量
    1. 1.1. Constant or Immutable ?
    2. 1.2. Shadow or Mutable?
  2. 2. 数据类型(基本量)
    1. 2.1. 整数:默认i32
      1. 2.1.1. Integer Overflow
    2. 2.2. 浮点:默认f64
    3. 2.3. Bool类型:True/False
    4. 2.4. 字符类型:32bit, Unicode
  3. 3. 数据类型(复合类型):tuple和array
    1. 3.1. tuple元组:参考python
    2. 3.2. array数组:没啥特别的
      1. 3.2.1. Index Out of Bound?
  4. 4. 函数
    1. 4.1. 函数声明:
    2. 4.2. 函数参数:
    3. 4.3. 函数语句和返回值:注意这里
  5. 5. 控制结构:
    1. 5.1. if-else
      1. 5.1.1. if的基本要求
      2. 5.1.2. 除了是控制语句以外…
    2. 5.2. 强大的match
      1. 5.2.1. 绑定值
    3. 5.3. if-let简单控制
    4. 5.4. 循环
      1. 5.4.1. loop:服务器
      2. 5.4.2. while
      3. 5.4.3. for: python,好用
  6. 6. 结构体struct
    1. 6.1. struct类型
    2. 6.2. struct方法
  7. 7. 枚举
    1. 7.1. 枚举定义和使用
    2. 7.2. Option枚举