Rust 基础
Rust 是个基于表达式 以及函数式编程的语言
use
- 这个东西就等于是
JS
中的import
、require
这种的引入包,插件的语法。
std
在标准库中 默认情况下 rust
会把一个叫 prelude
(也叫预导入) 这个模块的内容导入每个程序的作用域中,它也是 标准库
的一部分
如果我们要使用的类型不在这个 prelude
里,那么就必须要使用 use
这个关键字把这个类型显示的导入到程序中。
use std::io;
rand
是非标准库 也叫 trait
,可以看作是 JS
里面的 API
use rand::Rng;
let 、 mut
在 Rust 里面所有的变量默认都是不可变的 叫 immuteble ,在 JS 里面我们为了提高对象的性能会使用一个库叫 immuteble.js 来使对象编程是不可变对象的道理是类似的
- 相同:
let
就跟JS
里面的一样,但是在Rust
这门语言里,不加上mut
修饰符来修饰就等于是JS
里面的const
但不是Rust
中的常量。 - 不同: 变量可覆盖,
JS
中let
const
不可重新定义,var
可以 但这个概念叫shadowing
。
function main() {
let name = "ACE";
name = "Protgas.D.ACE";
}
fn main() {
let mut name = "ACE";
println!("Welcome, {} to the Rust world!", name);
name = "Protgas.D.ACE";
println!("Welcome, {} to the Rust world!", name);
}
:: 这个像 CSS
里面伪元素的东西到底是个啥呢?
这个 :: 是 关联函数,模块创建的命名空间
// 例如
use std::io // 引用 IO 库
let mut name = String::new(); // 返回字符串的一个新的实例
rust
里 字符串就是 String
这是标准库提供的,内部使用了 UTF8
格式的编码,并且可以按照需求进行扩展自己的大小,::
表明这个 new
是 String
类型的一个 关联函数
,
关联函数
就是针对类型本身来实现的,而不是针对字符串的某个特定实例来实现的。
关联函数
:就等于是 C#
或者是 Java
语言中的的静态方法一样, new
函数就等是一个空白的字符串,在 rust
里很多类型也有 new
函数,
在 rust
里面 new
这个名是创建类型实例的一个惯用函数名,所以这句话就是定义了一个可变的变量 name
,然后把它的值绑定到了一个空字符实例上面。
& 取地址符号
这个 “并且” 符号是个啥玩意呢?
std::io::stdin().read_line(&mut name).expect("这个啥错了");
& 表示这个 实参
是一个 引用
(reference),它允许多处代码访问同一处数据,而无需在内存中多次拷贝。
注意:引用在 Rust
里是一个复杂的特性, Rust
的一个主要优势就是安全而简单的操纵引用。它就像 JS
里面的 const
默认不可改。
&
就表示引用:允许你引用某些值而不取得所有权,引用在 rust
里面也是默认不可变的,必须要配合 mut
关键字让这个引用也变成可变的。
IO
库有个 stdin
stdin
返回类型叫 Stdin
的实例, stdin
会被用做句柄来处理终端中的标准输入。
我们把引用作为函数参数的这个行为叫做借用,并且只读。
在特定的作用域内,对于某一块数据,只能有一个可变的引用。
这样做的好处就是可在编译的时候防止数据竞争。
一下三种行为下会发生数据竞争
- 超过 1 个指针同时访问同一块数据
- 至少有一个指针用于写入数据
- 没有使用任何机制来同步对数据的访问
可以通过创建新的作用域,来允许非同时的创建多个可变引用
fn main() {
let mut s = String::from("hello world");
{
let s1 = &mut s;
}
let s2 = &mut s;
}
constant 常量
/**
* 跟 JS 中常量的区别
* - 大写 不然警告
* - 可重复定义
* 跟 Rust 中 let 的区别
* - 不能 mut
* - 可以全局定义
* - 必须定义类型
*/
const MAX: u32 = 100_000; // 数字可以增加下划线增加可读性
shadowing 隐藏
可以重复声明变量
fn main() {
let x = 2;
let x = x + 1;
let x = x * 2;
// 猜猜等于啥
println!("{}", x);
}
shadowing
可以被任意类型重新赋值。
mut
只可以被相同类型赋值。
数据类型
标量
和 复合类型
Rust
是静态编译语言,在编译时必须知道所有变量的类型。- 基于使用的值,编译器通常能够推断出它的具体类型
- 但如果可能的类型比较多(例如把
String
转为整数的parse
方法),就必须添加类型的标注,否则编译就会报错。
fn main(){
let love:u32 = "520".parse().expect("这不是个数字");
println!("{}", love);
}
基于上面的案例 Rust
有很多的整数数据类型,所以要讲 love
这个变量具体的指明是什么类型,这样 parse
方法就知道应该把 "520" 这个字符串解析成什么类型了,如果不标注成什么类型
那么 love
就会报错,并且编译也会报错,因为 Rust
的 parse
方法 和 编译器不知道把 "520" 这个字符串字面值解析成哪种的整数类型。
fn main(){
let love = "520".parse().expect("这不是个数字");
println!("{}", love);
}
标量类型
- 一个标量类型代表一个单个的值。
Rust
有四个主要的标量类型。
整数类型
- 没有小数部分
- 例如 u32 就是一个无符号的整数类型,占据 32 位的空间。
- 无符号整数类型以 u 开头,u 是 unsigned 的意思。
- 有符号整数类型以 i 开头。
- Rust 的整数类型对照表如图
- 每种都分为 i 和 u , 以及固定的位数。
- 有符号范围:
负 2 的 n 次方减 1
到2 的 n 减 1 次方减 1
;
- 无符号范围:
0 到 2 的 n 次方减 1
Length | Signed | Unsigned |
---|---|---|
8-bit | i8 | u8 |
16-bit | i16 | u16 |
32-bit | i32 | u32 |
64-bit | i64 | u64 |
128-bit | i128 | u128 |
arch | isize | usize |
arch:系统架构 isize usize 根据系统位数决定 如果是 32 位 分别就是 i32 u32
整数字面值
Number Literals | Example |
---|---|
Decimal | 98_222 |
Hex | 0xff |
Octal | 0o77 |
Binary | 0b1111_0000 |
Byte(u8 only) | b'A' |
- Decimal: 10 进制 可以加 _增加可读性
- Hex: 16 进制 0x 开头
- Octal: 8 进制 0o 开头
- Binary: 2 进制 0b 开头 可以加_ 增加可读性
- Byte: 仅限 u8 类型 b 开头跟着一个字符
- 除了
Byte
,所有的数值字面值都允许使用类型后缀。 例如27u8
。如果不确定哪种类型,可以使用整数默认的类型i32
:- 总体上来说速度很快,即使在 64 位的系统中。
无符号整数类型包括以下几种:
- u8,占用 8 位(即 1 个字节),范围在 [0, 255] 之间;
- u16,占用 16 位(即 2 个字节),范围在 [0, 2^16] 之间;
- u32,占用 32 位(即 4 个字节),范围在 [0, 2^32] 之间;
- u64,占用 64 位(即 8 个字节),范围在 [0, 2^64] 之间;
- u128,占用 128 位(即 16 个字节),范围在 [0, 2^128] 之间;
- usize,根据平台决定存储位数,在 32 位平台下占用 32 位,在 64 位平台下占用 64 位;
有符号整数类型包括以下几种:
- i8,占用 8 位(即 1 个字节),范围在 [-128, 127] 之间,相当于 Java 中的 byte 类型;
- i16,占用 16 位(即 2 个字节),范围在 [-2^15, 2^15 - 1] 之间,相当于 Java 中的 short 类型;
- i32,占用 32 位(即 4 个字节),范围在 [-2^31, 2^31 - 1] 之间,相当于 Java 中的 int 类型;
- i64,占用 64 位(即 8 个字节),范围在 [-2^63, 2^63 - 1] 之间,相当于 Java 中的 long 类型;
- i128,占用 128 位(即 16 个字节),范围在 [-2^127, 2^127 - 1] 之间;
- isize,根据平台决定存储位数,在 32 位平台下占用 32 位,在 64 位平台下占用 64 位;
整数溢出
- 例如:
u8
的范围是0-255
,如果把一个u8
变量的值设为256
,那么就会报错:- 调试模式下编译:
Rust
会检查整数溢出,如果发生溢出,程序在运行时就会panic
(恐慌)。 - 发布模式下(--release)编译:
Rust
不会检查可能导致的panic
的整数溢出。- 如果溢出发生:
Rust
会执行 “环绕” 操作:- 256 变成 0 , 257 变成 1...
- 但程序不会
panic
。
- 如果溢出发生:
- 调试模式下编译:
the literal '256' does not fit into the type 'u8' whose range is '0..=255'
浮点
f32
,单精度浮点数,占用 32 位(即 4 个字节),相当于Java
中的float
类型;f64
,双精度浮点数,占用 64 位(即 8 个字节),相当于Java
中的double
类型;
Rust
的浮点类型使用了 IEEE-754 标准来描述。默认类型是 f64。
f64
是默认类型,因为在现代 CPU
上 f64
和 f32
的速度差不多,而且精度更高。
数值操作
加减乘除余
fn main(){
let sum = 1 + 2;
let difference = 9.5 - 4.1;
let product = 2 * 25;
let quotient = 25.7 / 12.2;
let reminder = 32 % 5;
}
布尔 bool
占 1 个字节
字符类型
Rust
语言中char
类型被用来描述语言中最基础的单个字符。- 字符类型的字面值使用单引号。
- 字面值使用单引号 占 4 个字节 是 Unicode 标量值,可以表示比 ASCII 多得多的字符内容:拼音、中日韩文、零长度空白字符、emoji 表情等。
- U+0000 到 U+D7FF
- U+E000 到 U+10FFFF
Unicode 并没有字符的概念,所以直接上认识的字符也许跟 Rust
中的概念并不相符。
复合类型
可以将多个值放到一个类型里面
Rust 提供了两种基础的复合类型:元祖(Tuple)、数组
元组 (Tuple)
Tuple
可以将多个类型的多个值放在一个类型里Tuple
的长度固定:一旦声明就无法改变
Rust
里面的元组跟 JS
里面的区别是这样的
const turple = ["ACE", "Luffy", "Zoro", "Nami", ...]
// 结构赋值 destructor
const [a, b, c, d] = turple;
let turple: (i32, f64, u8) = (520, 13.14, 1);
// 解构赋值
let (x, y, z) = turple;
println!("{},{},{}", x, y, z);
println!("{},{},{}", turple.0, turple.1, turple.2);
数组
Array
多个值放在一个类型里Array
每个元素类型必须保持一致Array
的长度固定:一旦声明就无法改变
如果想让你的数据存放在栈上,不是在堆上,或者想保证有固定数量的元素,这时使用数组更有好处
let array: [&str; 5] = ["路飞", "索隆", "娜美", "乌索普", "山治"];
let [a, b, c, d, e] = array;
println!("{},{},{},{},{}", a, b, c, d, e);
let same_array = ["路飞"; 5];
println!("{:?}", same_array);
JS
元祖就是数组,数组也是元祖,这样理解就可以了,Rust
元祖和数组是不一样的,元祖的元素可以由任意类型组成,数组必须统一类型。JS
的数组可以理解为不限制长度2^32
,不需要定义长度,Rust
必须定义长度。JS
的数组是在堆内存,Rust
的数组是在栈上分配的单个块的内存。
Array
是由 prelude
模块提供的 Vector
是由 标准库
提供的,并且更灵活,长度可以改变
访问数组的元素
- 数组是 Stack 上分配的单个块的内存
- 可以使用索引来访问数组的元素(例子)
let months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
let first = months[0]
- 如果访问的索引超出了数组的范围,那么:
- 编译会通过
- 运行时报错(runtime 发生 panic)
Rust
不允许其继续访问响应地址的内存。
let first = months[12];
println!("{}", first);
error: this operation will panic at runtime
--> src\main.rs:4:17
|
4 | let first = months[12];
| ^^^^^^^^^^ index out of bounds: the length is 12 but the index is 12
|
= note: `#[deny(unconditional_panic)]` on by default
error: could not compile `lesson5` due to previous error
函数
- JS 没有规范,可以任意的命名。 Rust 函数名必须小写 ,多个单词用 _ 隔开 并且 fn 关键字
- 定义返回值类型是在单箭头的后面定义
- 最后一句表达式不加分号就不用 return 关键字。
fn demo(x: i32) -> i32 { // parameter
1
}
demo(5) // arguments