前端er快速入门rust基础
title: 前端er快速入门rust基础
id: 488359a7-58bb-48f9-830f-64087c46aa06
date: 2024-04-03 11:46:13
auther: lvzy
cover: https://io.1love.pub/38d5/Monica_2024-04-03_13-26-42.png
excerpt: 前端er快速入门rust基础 前端现在环境太好啦~ 外包都不要民办大学的人了。 😑,苦前端久已。 不如直接入手rust。这里通过我一个前端小菜鸟的角度快速入门rust 材料准备 官网地址 文档地址 原版 中文版本 环境
permalink: /archives/1712115972991
categories:
- xue-xi
- cai-keng
tags: - rust
wen-dang
前端er快速入门rust基础
前端现在环境太好啦~
外包都不要民办大学的人了。
😑,苦前端久已。
不如直接入手rust。这里通过我一个前端小菜鸟的角度快速入门rust
材料准备
看文档自行安装
安装完成后可以用cargo –version做测试
变量
这里简单说下,rust中的命名标志符跟ts有点像。但是功能有点差距。
let
借用官网的例子
fn main() {
let x = 5;
println!("The value of x is: {x}");
x = 6; // ❌
println!("The value of x is: {x}");
}
单纯使用let定义的变量是不可变的,需要使用mut 关键字定义
fn main() {
let mut x = 5;
println!("The value of x is: {x}");
x = 6;
println!("The value of x is: {x}");
}
以上是同类型的变更。
但是!如果你需要变更数据类型,比如字符串转数字,需要使用let 重新定义
这里就跟js中不同了哦,js是个弱类型,你可以随便改~跟ts也不同,ts需要写联合类型或者断言。
let mut guess: String = String::new(); // 原类型string
let guess: u32 = guess.trim().parse().expect("Please type a number");
// 经过parse过后,转换成u32类型
// 此处的u32就是类型啦,跟ts中的number差不多。具体可看下文
此处的excpet 代表转换出错,会输出Please type a number
关于let的shadowing
这里简单说下,在rust中,你可以对同名的变量一直使用let修改。但是这里涉及到一个作用域的概念。
作用域
function main() {
let x: number = 5;
x = x + 1;
{
let x: number = x * 2; // ❌ ReferenceError: Cannot access 'x' before initialization
console.log(`The value of x in the inner scope is: ${x}`);
}
console.log(`The value of x is: ${x}`);
}
main();
在ts或者js中在花括号作用域中重新声名let x ,会导致一个问题,也就是代码中提到的Cannot access 'x' before initialization,无法在变量初始化之前访问,这是js的知识,变量提升~但是在rust中,这样的写法是合理合规的:
fn main() {
let x = 5;
let x = x + 1;
{
let x = x * 2;
println!("The value of x in the inner scope is: {x}");
}
println!("The value of x is: {x}");
}
```shell
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/variables`
The value of x in the inner scope is: 12
The value of x is: 6
这里可以看到,输出了12 和6; 说明以下情况:作用域中的let不会修改外部的值!
这就是rust所谓的Shadowing,内部作用域中重新let变量,不会导致外部变量发生改变~
const
const就是完全不能改变的常量!
const 不允许使用mut !并且,必须使用类型修饰!并且,const只能是固定值,不能是其他变量合并的值!
变量类型
!记住官网的话,rust是静态类型语言!
这就意味着在代码编译前,就应该确定所有变量、常量的类型。
这里简单说下跟ts的差别
Number
ts中只要是数字,都可以使用Number进行类型修饰,或者对于超大类型的bigint。
在rust中要分成以下几种
| 长度 | 有符号 | 无符号 |
| ——- | —— | :—-: |
| 8-bit | i8 | u8 |
| 16-bit | i16 | u16 |
| 32-bit | i32 | u32 |
| 64-bit | i64 | u64 |
| 128-bit | i128 | u128 |
| arch | isize | usize |
这种写法熟悉其他语言的应该比较清楚,其实就是个数字大小限制
比如 n-bit
,就是 -(2n - 1) 到 2n - 1 - 1
在内的数字。
最后的arch就是依赖于计算机架构的,计算机架构为32位,他就是32,以此类推。
关于溢出
既然限制了变量大小,那么一定也有溢出的问题存在。
比如:
let x:u8 = 244;
x = x + 8888; // ❌
在编译的时候会直接报错,但是在release的时候并不会检查这个。
此时,可以使用显式处理溢出:
- check_*
let x:u8 = 244;
match x.check_add(8888) {
Some(value) => println!("Sum: {}", value),
None => println!("Overflow occurred!"),
}
- wrapping_*
let x:u8 = 244;
let sum = x.wrapping_add(8888);
println!("Sum: {}", sum);
- overflowing_*
let x:u8 = 244;
let (sum, overflow) = x.overflowing_add(8888);
if overflow {
println!("Overflow occurred!");
} else {
println!("Sum: {}", sum);
}
- saturating_*
let x:u8 = 244;
let sum = x.saturating_add(8888);
println!("Sum: {}", sum);
一共有四种:
所有模式下都可以使用 wrapping_* 方法进行 wrapping,如 wrapping_add
如果 checked_* 方法出现溢出,则返回 None值
用 overflowing_* 方法返回值和一个布尔值,表示是否出现溢出
用 saturating_* 方法在值的最小值或最大值处进行饱和处理
float
这里需要关注,rust与java等语言相同,浮点类型的需要使用f32或者f64 ,所有的浮点型数据都是有符号的!
复合类型
复合类型有两个原生的,元组和数组
元组没啥好讲的,跟ts一样,固定长度,固定类型!
简单例子:
let tup:(i8,f32,u8) = (12,1.33,2)
// 访问方式
let (x,y,z) = tup; // 此处类似解构赋值
let a = tup.0;
// 0 代表位置,类似于数组的角标
数组
Rust中的数组也是定长的!需要谨记!如需要动态增减长度,需要使用另外的方式来做定义。
定义定长数组有三种方式:
let arr1 = [1, 2, 3, 4, 5]; // 第一种方式,自动推断类型
let arr2: [i32; 5] = [1, 2, 3, 4, 5]; // 第二种方式,显示声明类型
let arr3 = [3; 5]; // 第三种方式,初始化为相同的值,也就是 3是每个元素的值,5为长度。
取值方式跟js没有区别,也是用数组下标进行获取。
let len: usize = arr1.len();
// .. 为左闭右开区间 [0, len) 0 <= i < len ..= 为左闭右闭区间 [0, len] 0 <= i <= len
for i in 0..len {
println!("{}", arr1[i]);
}
这里需要记住 ..* 操作符,后面有讲。
Vec 向量
这个东西就比较像数组了,但是定义方式不一样:
let mut vec: Vec<u8> = Vec::new(); // 定义空向量
let mut vec2 = vec![1, 2, 3, 4, 5]; // 定义有默认值的向量
当然,我们在js中定义的数组元素可以是任意类型的,ts中可以用any大法或者联合类型,或者接口来实现。在rust中需要使用枚举类型来操作~当然枚举类型可用与多处,这里简单写一下向量的定义
enum MutPtr {
Number(u32),
Text(String),
}
let mut vec3: Vec<MutPtr> = Vec::new();
vec3.push(MutPtr::Number(8));
vec3.push(MutPtr::Text(String::from("123123131")));
for element in vec3.iter() {
match element {
MutPtr::Number(value) => println!("Number: {}", value),
MutPtr::Text(value) => println!("Text: {}", value),
}
}
越界问题
无论是哪一种语言都会出现数组越界的问题,js中数组越界不会导致程序崩溃,rust会!所以我们得用一种合适的方式去处理:
使用get去获取数组或者向量的option
let arr = [1, 2, 3]; if let Some(element) = arr.get(2) { println!("The third element is {}", element); } else { println!("Index out of bounds."); }
通过length去处理
let arr = [1, 2, 3]; let index = 2; if index < arr.len() { println!("The element is {}", arr[index]); } else { println!("Index out of bounds."); }
copy类型和move类型
这个东西在rust中很重要,也是很多人不理解的地方。
在rust中,所有的基本类型都是copy类型,也就是说,当你把一个变量赋值给另一个变量的时候,他们是两个独立的变量,互不影响。
用js的方式来做说明:
copy类型就是基本类型,move类型就是引用类型。
但是在rust中,move类型会在赋值的时候,把原来的变量移动到新的变量中,也就是说,原来的变量就不能再使用了。
意思就是,当你使用一个变量赋值给另一个变量的时候,他们是move类型的,也就是说,他们是同一个变量,只是在内存中的位置不同。原来的变量会被删除,当你访问的时候,会报错。
copy类型
整数类型、浮点数类型、bool类型、char类型、元组类型、数组类型
let x = 5;
let y = x;
println!("x = {x}, y = {y}");
// x = 5, y = 5
let mut arr1 = [1, 2, 3, 4, 5];
let test = arr1;
arr1[2] = 100;
println!("{:?}", arr1);
println!("{:?}", test);
// [1, 2, 100, 4, 5]
// [1, 2, 3, 4, 5]
这里的x和y是两个独立的变量,互不影响。
也可以看到,数组的操作也是一样的,他们是copy类型的。
move类型
字符串类型、Vec类型、函数类型、闭包类型、自定义类型
但是,当你把一个变量赋值给另一个变量的时候,他们是move类型的,也就是说,他们是同一个变量,只是在内存中的位置不同。
let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1); // ❌
let mut arr1 = vec![vec![1, 2, 3], vec![4, 5, 6]];
let test = arr1; // 这里发生了所有权的转移
// arr1[0][2] = 100; // 这行代码将会导致编译错误,因为 arr1 的所有权已经被转移
println!("{:?}", test);
这里会报错,因为s1已经被移动了,所以不能再使用。
上面copy类型的例子中,我们可以看到,arr1[2] = 100; 这行代码是可以执行的,但是在move类型中,这行代码是不可以执行的,因为所有权已经被转移了。为什么呢?
因为move的数组例子中,arr1中的元素是引用类型,所以在move的时候,只是把引用的所有权转移了,而不是引用的值。
由此我们可以得到一个结论:
在 Rust 中,如果数组内的元素类型实现了 Copy trait(比如基本的数值类型、字符类型等),那么对这个数组使用 = 操作进行赋值时,会自动进行元素级别的复制,这种情况下不会产生所有权或借用的问题