Skip to main content

Rust 基础

Rust 是个基于表达式 以及函数式编程的语言

use

  • 这个东西就等于是 JS 中的 importrequire 这种的引入包,插件的语法。
std

std 在标准库中 默认情况下 rust 会把一个叫 prelude(也叫预导入) 这个模块的内容导入每个程序的作用域中,它也是 标准库 的一部分 如果我们要使用的类型不在这个 prelude 里,那么就必须要使用 use 这个关键字把这个类型显示的导入到程序中。

use std::io;
rand

rand 是非标准库 也叫 trait ,可以看作是 JS 里面的 API

use rand::Rng;

let 、 mut

在 Rust 里面所有的变量默认都是不可变的 叫 immuteble ,在 JS 里面我们为了提高对象的性能会使用一个库叫 immuteble.js 来使对象编程是不可变对象的道理是类似的

  • 相同: let 就跟 JS 里面的一样,但是在 Rust 这门语言里,不加上 mut 修饰符来修饰就等于是 JS 里面的 const 但不是 Rust 中的常量。
  • 不同: 变量可覆盖,JSlet const 不可重新定义, var 可以 但这个概念叫 shadowing
JS 里面是这样的
function main() {
let name = "ACE";
name = "Protgas.D.ACE";
}
Rust 里面是这样的
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 格式的编码,并且可以按照需求进行扩展自己的大小,:: 表明这个 newString 类型的一个 关联函数关联函数 就是针对类型本身来实现的,而不是针对字符串的某个特定实例来实现的。

关联函数:就等于是 C# 或者是 Java 语言中的的静态方法一样, new 函数就等是一个空白的字符串,在 rust 里很多类型也有 new 函数, 在 rust 里面 new 这个名是创建类型实例的一个惯用函数名,所以这句话就是定义了一个可变的变量 name ,然后把它的值绑定到了一个空字符实例上面。

& 取地址符号

这个 “并且” 符号是个啥玩意呢?

std::io::stdin().read_line(&mut name).expect("这个啥错了");
caution

& 表示这个 实参 是一个 引用(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 的区别

shadowing 可以被任意类型重新赋值。

mut 只可以被相同类型赋值。

数据类型

标量复合类型

  • Rust 是静态编译语言,在编译时必须知道所有变量的类型。
    • 基于使用的值,编译器通常能够推断出它的具体类型
    • 但如果可能的类型比较多(例如把 String 转为整数的 parse 方法),就必须添加类型的标注,否则编译就会报错。
🌰

fn main(){

let love:u32 = "520".parse().expect("这不是个数字");

println!("{}", love);

}

基于上面的案例 Rust 有很多的整数数据类型,所以要讲 love 这个变量具体的指明是什么类型,这样 parse 方法就知道应该把 "520" 这个字符串解析成什么类型了,如果不标注成什么类型 那么 love 就会报错,并且编译也会报错,因为 Rustparse 方法 和 编译器不知道把 "520" 这个字符串字面值解析成哪种的整数类型。


fn main(){

let love = "520".parse().expect("这不是个数字");

println!("{}", love);

}

标量类型

  • 一个标量类型代表一个单个的值。
  • Rust 有四个主要的标量类型。

整数类型

  • 没有小数部分
  • 例如 u32 就是一个无符号的整数类型,占据 32 位的空间。
  • 无符号整数类型以 u 开头,u 是 unsigned 的意思。
  • 有符号整数类型以 i 开头。
  • Rust 的整数类型对照表如图
    • 每种都分为 i 和 u , 以及固定的位数。
    • 有符号范围:
      • 负 2 的 n 次方减 12 的 n 减 1 次方减 1;
    • 无符号范围:
      • 0 到 2 的 n 次方减 1
LengthSignedUnsigned
8-biti8u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
archisizeusize

arch:系统架构 isize usize 根据系统位数决定 如果是 32 位 分别就是 i32 u32

整数字面值

Number LiteralsExample
Decimal98_222
Hex0xff
Octal0o77
Binary0b1111_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。

tip

f64 是默认类型,因为在现代 CPUf64f32 的速度差不多,而且精度更高。

数值操作

加减乘除余

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 里面的区别是这样的

JS 元组
const turple = ["ACE", "Luffy", "Zoro", "Nami", ...]

// 结构赋值 destructor
const [a, b, c, d] = turple;
Rust 元组
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 的长度固定:一旦声明就无法改变
注意

如果想让你的数据存放在栈上,不是在堆上,或者想保证有固定数量的元素,这时使用数组更有好处

Rust 数组
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]

caution
  • 如果访问的索引超出了数组的范围,那么:
    • 编译会通过
    • 运行时报错(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