关于前端:Rust入门系列之语言特性-1

35次阅读

共计 4250 个字符,预计需要花费 11 分钟才能阅读完成。

1. 概述

Rust 是一门编译型的语言(AOT – ahead of time),生成可执行代码前须要先编译,这是和 JavaScript 等解释型语言基本上的区别。

2. 变量与可变性

Rust 中通过 let 来申明变量,但 let 申明的变量默认是不可变 (_Immutable_) 变量

let 申明变量后尝试批改它的值,编译时就会报错。

fn main() {
    let a = 5;
    a = 6; // error: cannot assign twice to immutable variable
}

咱们能够应用以下两种形式来解决这个问题

  • let 申明变量时应用 mut 关键字进行润饰,示意它是一个可变变量。须要留神的是,Rust 是强类型语言,所以即便申明为可变变量,也只能从新赋值为雷同数据类型的值

    fn main() {
      let mut a = 5;
      a = 6;
      // 扭转数据类型,编译报错
      a = "6"; // error: expected integer, found `&str`
    }
  • 应用 shadowing 个性,再次申明此变量笼罩之前的值,并且不受之前数据类型的限度。相当于从新申明后,之前的变量就被暗藏了

    fn main() {
      let a = '5';   // '5'
      let a = 5;     // 5
      let a = a + 1; // 6
      let a = a * 2; // 12
    }

3. 常量

Rust 中通过 const 关键字申明常量,常量与变量的区别是

  • 不能够应用 mut 关键字
  • 常量在申明时必须指定数据类型
  • 常量能够在任何作用域申明,包含全局作用域
  • 常量只能够绑定到常量表达式

命名标准:字母全副大写,单词之间通过 _ 连贯,如

const MAX_AGE: u32 = 10_0000;

4. 数据类型

4.1 标量类型

4.1.1 整数类型

  • 有符号整数应用 i 结尾 $$[-(2^n-1) , 2^{n-1}-1]$$
  • 无符号整数应用 u 结尾 $$[0 , 2^n – 1]$$
  • isize 和 usize 类型的位数是由程序运行的计算机的架构所决定,运行在 64 位的计算机上,则是 64 位的
  • 整数的默认类型为 i32

4.1.1.1 整数字面值

  • 16 进制应用 0x 结尾
  • 8 进制应用 0o 结尾
  • 2 进制应用 0b 结尾
  • byte 类型的数据类型仅限 u8,应用 b 结尾

除了 byte 类型外,所有数值的字面值都容许应用类型后缀,如

// 示意 u8 类型的数值 57
let a = 57u8;

4.1.1.2 整数溢出

如果把一个 u8(0-255)类型的变量设置为 256,会有一下状况:

  • 开发模式下,Rust 检测到溢出,在程序运行时就会 panic
  • 公布模式下,Rust 不会检测溢出,当产生溢出时会执行盘绕操作:

    • 256 -> 0
    • 257 -> 1

4.1.2 浮点类型

  • f32,单精度
  • f64,双精度(Rust 浮点数的默认类型,因为在古代 CPU 上 f64 和 f32 的速度差不多)
  • Rust 浮点类型应用了 IEEE-754 规范

4.1.3 布尔类型

  • 占用一个字节
  • 符号为 bool,值为 true | false

4.1.4 字符类型

  • 符号为 char
  • 字面值应用单引号

4.2 复合类型

4.2.1 元组(tuple)

  • 申明后长度不可变
  • 可将不同数据类型的值组合到一起

    // 申明一个元组
    let tup: (i32, f64, u8) = (500, 5.6, 23);
    
    // 获取元组成员
    
    // 1. 解构
    let (x, y, z) = tup;
    println!("{}, {}, {}", x, y, z); // 500, 5.6, 23
    
    // 2. 点标记法
    println!("{}, {}, {}", tup.0, tup.1, tup.2); // 500, 5.6, 23

4.2.2 数组

  • 和 tuple 一样,申明后长度不可变
  • 数组成员的数据类型必须雷同
  • 数组是存在栈内存中
  • 数组的类型通过 [type; length] 的模式示意

    let arr: [i32; 5] = [1,2,3,4,5];
    
    // 非凡的数组申明办法
    let sameMemberArray = [3; 5]; // [3,3,3,3,3]
    
    // 拜访数组成员
    let f = arr[0]; // 1
    
    // 拜访数组越界 - rust 编译器会进行简略的越界查看
    let more = arr[6]; // 编译报错
    
    // 拜访数组越界 - rust 编译器检测不到的场景
    let temp = [6,7,8,9];
    let more_temp = arr[temp[0]]; // 编译通过,运行时报错

5. 函数

  • Rust 中应用 fn 关键字申明函数
  • 对于函数和变量名,应用 snake case 标准来命名(单词小写,应用 _ 拼接)
  • 参数在定义时必须指定数据类型
  • 如果要提前返回一个值,则应用 return 关键字
  • 函数默认返回值是一个空元组

    fn main() {let number = get_number(5);
      
      println!("{}", number); // 10
    }
    
    fn get_number(a: i32) -> i32 {
      // 函数中最初行如果是表达式,那么表达式的值则会作为函数的返回值
      // 如果行尾加了分号,则会被辨认为语句,就不会被作为函数的返回值
      a + 5
    }

6. 控制流

6.1 条件分支 – if

  • 须要留神的是,每个分支块如果有返回值,必须保障数据类型雷同

    let a = 3;
    
    // if else
    if a == 3 {println!("a is 3");
    } else {println!("a is not 3");
    }
    
    // 应用 表达式 的个性达到其余语言中 三元表达式 的成果
    let b = if a == 3 {5} else {6}; // 5 

6.2 循环

  • loop

    • loop 会有限循环执行循环体中的代码,直到被 break 中断
    • break 能够为 loop 循环的表达式提供一个返回值
    // loop 循环
    let mut count = 0;
    
    let value = loop {
      count += 1;
      
      if count == 10 {break count * 2;}
    }
    
    println!("{}", value); // 20
  • while

    let arr = [1,2,3,4,5];
    let mut index = 0;
    
    // 应用 while 循环遍历数组
    while index < 5 {println!("{}", arr[index]);
      
      index = index + 1;
    }

  • for

    let arr = [1,2,3,4,5];
    
    for item in arr.iter() {println!("for item {}", item);
    }
    
    // 应用 range 实现指定次数的循环
    // 1. (1..5) -> 一个蕴含 1,2,3,4 的可迭代器
    // 2. 应用 rev 办法进行反转
    for item in (1..5).rev() {println!("range item is {}", item)
    }

7. 所有权

所有权是 Rust 无需 GC 就能保障内存平安的外围个性

7.1 内存和调配

当变量走出作用域范畴后,Rust 会主动调用 drop 函数将内存空间交还给操作系统

7.2 Stack 上的数据复制:copy

  • 对于简略的标量数据类型,以下代码最终会向 stack 中压入两个 5

    • 实质上是因为标量类型实现了 Copy trait
    • Copy 这个 trait 用于相似整数这种寄存在 stack 上的数据类型,须要分配内存的数据类型都不能实现这个 trait
    • 如果实现了 Copy trait,那么旧变量在赋值前任然能够应用
    • 如果一个类型实现了 Drop trait,那 Rust 就不容许再实现 Copy trait 了
    • 领有 Copy trait 的数据类型

      • 整数
      • 浮点数
      • 布尔
      • 字符
      • 元祖 (须要满足成员都是领有 Copy trait 的数据类型)
    let x = 5;
    let y = x;
    
    println!("{}, {}", x, y); // 5, 5

7.3 变量和数据交互的形式:move

  • 对应长度未知的复合数据类型,将一个变量赋给另一个变量后,前者就会生效(在 Rust 中被称作 move,即 原先 s1 指向的内存空间挪动到了 s2,实现挪动后,s1 便生效了,由此来防止 s1 和 s2 走出作用域时对同一内存空间产生两次开释操作)

    • 二次开释(double free)在其余须要手动管制内存的语言中是重大的 bug,可能开释掉正在被其余程序所应用的内存,导致未知的问题
    let s1 = String::from("haha");
    let s2 = s1;
    
    println!("{}", s1); // error: value borrowed here after move

7.4 所有权与函数

  • 其实把值传递给函数和变量的状况是相似的,将产生 move 或 copy

    fn main() {let string = String::from("hello");
    
      // Move,所有权产生挪动,传入到了函数作用域中
      move_case(string);
    
      /*
       * 在调用 move_case 时,string 指向的内存空间 的所有权产生了 move,* 当 move_case 调用结束时,string 指向的内存空间曾经被开释了,* 所以之后再拜访 string,编译时就会报错
       */
      // println!("{}", string); // value borrowed here after move
    
      // ---------------------------------------------------------------------
    
      let number = 12;
    
      // Copy,传入 number 值的正本
      copy_case(number);
    
      /*
       * number 是简略的标量类型,实现了 Copy trait
       * 在调用 copy_case 时,仅仅是传入了一个正本,* 所以后续任然能够持续应用
       */
      println!("{}", number);
    
      // ---------------------------------------------------------------------
    
      let bar = String::from("bar");
    
      /*
       * 在以下函数的调用过程中,bar 指向的内存空间的 所有权 被挪动到了 函数作用域 中,* take_ownership_and_return 这个函数的作用是失去一个内存空间的所有权并将其返回,* 最终 foo 拿到了该内存空间的 所有权,其实这段代码的成果与 let foo = bar; 雷同
       */
      let foo = take_ownership_and_return(bar);
    
      println!("{}", foo)
    }
    
    fn move_case(string: String) {println!("{}", string);
    }
    
    fn copy_case(number: u32) {println!("{}", number);
    }
    
    fn take_ownership_and_return(s: String) -> String {s}

8. Stack & Heap

  • stack 是间断的内存空间
  • heap 是散列的内存空间,指向 heap 内存 的指针是存储在 stack 中的
  • 所有存储在 stack 上的数据必须领有已知的大小,编译时大小未知的数据或运行时大小可能发生变化的数据必须存储在 heap 上

正文完
 0