Rust 基础知识

发布时间: 更新时间: 总字数:3460 阅读时间:7m 作者: IP上海 分享 网址

本文介绍Rust的标识符、关键字、操作符、常量、变量、指针等基础概念。

安装

  • Mac/Linux
# install, auto: echo "source $HOME/.cargo/env" >> ~/.bash_profile
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# update
rustup update

# uninstall
rustup self uninstall
  • 查看版本
$ rustc --version
rustc 1.62.0 (a8314ef7d 2022-06-27)

格式:rustc <version> (<commit hash> YYYY-mm-DD)

  • help 文档,运行如下命令打开本地浏览器
$ rustup doc

开发工具

  • vscode + rust-analyzer/Rust Syntax 插件
  • Clion + Rust 插件

Hello World

  • hello_world.rs
fn main() {
    println!("Hello, world!");
}

说明:

  • 文件名需要以 .rs 后缀结尾

  • 命名规范:使用英文小写,_ 分隔

  • fn 声明函数

  • main() 主函数名,无参数和返回值

  • 缩进使用4个空格(非tab)

  • println! 是 rust 的宏(macro),其中 ! 表示为宏,不带 ! 表示为函数

  • 代码以 ; 结尾

  • 编译:rustc 源文件名

$ rustc hello_world.rs
$ ls
  • 运行,运行时不依赖 rust
$ ./hello_world
Hello, world!

约定

  • Rust 的函数名、变量名、文件名一般采用 snake case 命名规范:所有字母均小写,单词使用下划线 _ 分隔
  • Rust 的代码包称为 crate,可以在 https://crates.io/ 查询,分类:
    • lib 库(库 crate)
    • 可执行文件

基本元素

  • Rust 是静态强类型语言,支持类型推动

关键字

  • as 类型转化,let x: usize = 10; let y = x as i32;
  • break
  • enum 声明枚举类型
  • let 声明变量
  • match 控制流运算符
  • fn 声明函数
  • use 导入路径到当前工作空间
  • as 为导入的路径指定本地别名
  • loop
  • while
  • for
  • struct 定义结构体
  • super 用来访问父模块路径中的内容,类似于文件系统的 ..
  • type 类型别名
  • return 返回值
  • & 取引用,如:&String
  • pub 公有
  • trait 定义trait,类似于其他语言的接口
  • move 所有权移交
  • unsafe 不安全代码块
  • extern 调动外部代码

区别:

  • let vs const
    • let 初始化时不确定,且声明与赋值可以分开,初始化无类型推导,可以加类型注释
    • const 必须用来声明常量,初始化后不可修改
  • shadow(隐藏,后面有介绍) vs let
    • let 声明的非 mut 同名新变量,是不可变变量,重新赋值会报编译错误
    • let 声明的同名新变量(shadowing)可以改变类型

常量

Rust 使用 const 声明常量(constant),规则如下:

  • 常量使用 const 声明,不需要加mut(区别与不可变变量),常量声明后不可变
  • 常量声明必须标注类型
  • 常量可以在任意作用域声明,如全局
  • 常量使用全大写,多个单词间使用 _ 分隔,示例如下:
const MAX_CPU: u32 = 10_000; // 值为1万,下划线增强可读

常量 vs 不可变静态变量

  • 静态变量,声明:static [mut] SCRESMING_SNAKE_CASE: &str = "hello world!";
    • 有固定的的内存地址,使用它的值总会访问相同的数据
    • 可以是可变的,访问和修改静态可变变量是不安全(unsafe)的
  • 常量
    • 允许使用时对数据进行复制

变量

Rust 使用 let 声明变量,规则如下:

  • 默认情况下为不可变(immutable)变量,如:let foo = 1;
  • let mut 声明的可变(mutable)变量,如:let mut bar = 2;
  • Rust 中可以使用相同的名字声明新变量,且新变量会 Shadowing(隐藏) 之前声明的同名变量
  • 以下划线 _ 开头命名的变量,编译器不会警告未被使用

示例如下:

fn main() {
    // 下面声明的变量是 mutable (可变)的
    let mut foo = 1;
    println!("{}", foo);

    foo = 2;
    println!("{}", foo);

    let foo = "abc";
    println!("foo is {}", foo);
    // shadowing var
    let foo = foo.len();
    println!("foo len is {}", foo);
}

其中,{} 表示占位符,与 python format 类似

数据类型

Rust 是静态编译语言,编译时需要确定所有变量的类型

  • 根据值,编译器可以推断出变量类型
  • 若值的类型可能比较多,必须添加类型标注,否则编译报错,如:
    let num: i32 = "10".parse().expect("Not a number");
  • 标量类型,共有4个:
    • 整数类型,默认为 i32
      • i8/i16/i32/i64/i128/isize 有符号整数类型,范围:$-(2^n - 1)$ ~ ($2^{n-1} - 1)$
      • u8/u16/u32/u64/u128/usize 无符号整数类型,非负,范围:0 ~ $2^n - 1$
      • size 由操作系统架构决定,32或64
      • 字面值/量,下面除 byte 类型外,其他数值允许使用类型后缀,如:24u16 表示 24u16 类型
        • 十进制 Decimal,如:10_000
        • 十六进制 Hex,如:0x1a
        • 八进制 Octal,如:0o11
        • 二进制 Binary,如:0b0001
        • 字节 Byte(u8 only),如:b’A'
      • 整数溢出,存在两种情况。以 u8(范围 0~255)类型为例,如果将 u8 变量的值设置为 256:
        • 调试模式下:Rust 检查到溢出,程序运行时会 panic
        • 发布模式下(--release)编译:Rust 不检查可能的 panic 溢出,若发生溢出,Rust 会执行 环绕 操作,即对 256 取余,256 转化为 0,257 转化为 1 …。程序不会 panic
    • 浮点类型,默认为 f64,采用 IEEE-754 标准表达
      • f32 单精度
      • f64 双精度
    • 布尔类型,占用一个字节
      • bool,值:true、false
    • 字符类型,占用4个字节,字面值使用单引号,Unicode 变量值,可以用来表示拼音、中日韩文字、emoj表情等,但 Unicode 中并没有 字符 的概念
      • char
  • 复合类型:将多个值放在一个类型里,Rust 有两种复合类型:
    • 元组(Tuple):将多个类型的多个值放在一个类型里
      • 特点:长度固定,一旦声明,不能修改
      • 声明:在小括号里将值分开,每个位置对应一个类型,且个元素的类型不必相同。
      • 示例:let foo: (i32, f64) = (1, 2.3);
      • 获取:使用模式匹配来解构(destructure)Tuple 来获取元素的值,如:let (x, y) = foo;
      • 访问:Tuple 变量可以使用点标记法+索引获取 .,访问 foo 时使用 foo.0foo.1 分别查看 i32 和 f64 类型对应的值
    • 数组:将多个相同类型的值放在一个类型里,长度固定,声明后不可修改
      • 声明:中括号里,使用逗号,分隔
      • 示例:let a1 = [1,2,3,4];
      • 数组的类型表示形式:[类型; 长度],如:let a1: [i32; 4] = [1,2,3,4];
      • 声明数组每个元素值都相同的特例:let a2 = [3; 2]; 相当于 let a2 = [3, 3];
      • 访问数组的元素:a1[0];
      • 访问索引越界时,编译(可能)、运行时会报 panic 错误(index out of bounds: the len is x but the index is y
    • Vector:和数组类型,有标准库提供,长度可变

操作符

  • 算术运算符:+,-,*,/(5/2=2),%(5%2=1)

注释

  • // 注释一行,可以在行开头;语句或表达式后面
  • /* */ 注释多行
/* comment main info */
/*
some info
 */
fn main() {
    // main fn
    println!("Hello, world!");  // print info
}

String 类型

  • 组成:以下3部分组成,内容存放在 stack 上
    • 指针 ptr,指向存放字符串内容的内存指针,内容存放在 heap 上
    • 长度 len
    • 容量 capacity
  • 在 heap 上分配存储空间,支持存储编译时未知数量的文本
  • 一般使用 from 函数从字符串字母值创建 String 类型,如:let str = String::from("hello string")
    • :: 表示 fromString 类型下的函数
fn main() {
    let mut str = String::from("hello string");

    str.push_str("...");

    println!("{}", str)
}

移动(move)

当变量离开作用域时,会调用 drop 函数,并将 String 使用的 heap 内容释放,下面示例中s1、s2字符串指向的内存相同,会触发二次释放(double free)bug。因此,Rust 设计采用 移动(move) 机制,使 s1 失效,当离开 s1、s2 作用域时仅释放一次内存。这也体现了 rust 内存安全性。

  • Rust 设计原则:不会自动创建数据的深拷贝
let s1 = String::from("hello");
let s2 = s1;
// 以后使用 s1 报错:borrow of moved value | value borrowed here after move
  • 移动(move) 与下面的拷贝实现不同点在使原变量实现
    • 浅拷贝(shallow copy)
    • 深拷贝(deep copy)

克隆(Clone)

对 String 上指针指向的数据(heap)进行深度拷贝,需要使用 clone 方法,示例:

let s1 = String::from("hello");
let s2 = s1.clone();
// 以下 s1/s2 都有效

Copy trait

  • copy trait 可以用于像整数这样完全存放在 stack 上的类型
  • 若一个类型实现了 copy trait,那么旧的变量在复制后仍然可用
  • 若一个类型或者该类型的一部分实现了 drop trait,那么 Rust 不允许让它再去实现 copy trait
  • 拥有 copy trait 的类型:
    • 任何简单 标量的组合类型 都可以 Copy 的
    • 任何需要分配内存或某种资源的都不是 Copy 的
  • 拥有 copy trait 的类型:
    • 所有整数类型,如u8
    • bool
    • char
    • 所有浮点类型,如f32
    • Tuple(元组),如果所有的字段都是 Copy 的
      • (i32, i32) 是
      • (i32, String) 不是

语句 vs 表达式

Rust 是一种基于表达式的语言,需要区分下语句和表达式的异同:

  • 语句是执行一系列的动作指令,语句没有返回值(或默认的返回值为空tuple,即 ()
  • 表达式会计算产生一个值
  • 函数的定义也是语句
  • 表达式(expression)没有后面的 ;,语句以 ; 结尾
fn main() {
    let bar = {
        let x = 1;
        x + 10
    };

    println!("bar is {}", bar)
}

流程控制

条件判断 if else

  • if 表达式:根据条件(bool 类型)执行不同的代码分支
    • 与条件关联的代码块称为分支(arm
    • 后面可以跟 else ifelse,如果有多个 else if 时,推荐使用 match 重构
fn main() {
    let n = 2;

    // if demo
    if n > 5 {
        println!("{} > 5", n)
    } else if n == 5 {
        println!("{} = 5", n)
    } else {
        println!("{} < 5", n)
    }

    // use match instead of if
    match n.cmp(&5) {
        std::cmp::Ordering::Greater => println!("{} > 5", n),
        std::cmp::Ordering::Equal => println!("{} = 5", n),
        std::cmp::Ordering::Less => println!("{} < 5", n),
    };

    // if
    let b = if n == 2 {true} else {false};
    println!("n==2 is {}", b)
}

循环

Rust 有3中循环实现:loopwhilefor

loop

  • loop:循环执行一块代码,一般使用 break 关键字停止程序循环,break 也可以指定返回值
fn main() {
    let mut c = 0;

    let r = loop {
        c += 1;

        if c == 5 {
            break c;
        }
    };

    println!("result is {}", r)
}

while

  • while:每次执行循环前,判断一次循环条件,满足就执行
fn main() {
    let mut c = 0;

    while c != 5 {
        println!("current number is {}", c);
        c += 1;
    }
}

for

  • for:迭代指定的次数,由于其安全、简捷,比 whileloop 更常用
fn main() {
    let arr = [1, 2, 3, 4, 5];

    for num in arr.iter() {
        println!("{}!", num);
    }

    for (index, value) in arr.iter().enumerate() {
        println!("index: {}, value: {}", index, value)
    }
}
  • for 一般和标准库提供的 Range 一起使用,Range 需要指定范围 [start, end),且可以使用 rev 翻转,翻转遍历 1~9 示例:
fn main() {
    // not include 10
    for num in (1..10).rev() {
        println!("{}!", num);
    }

    // include 10
    for num in (1..=10).rev() {
        println!("- {}!", num);
    }
}

其他

as 别名

as 关键字为导入的路径指定本地别名,示例:

use std::fmt::Result as fmtResult;

super

fn abc() {}

mod some_mod {
    fn call_abc() {
        super::abc();  // 相对路径调用
        crate::abc();  // 决定路径调用
    }
}
Home Archives Categories Tags Statistics
本文总阅读量 次 本站总访问量 次 本站总访客数