Rust编程·基本概念

 

安装、vscode插件 struct/enum数据结构 模式匹配、错误跳转、错误处理、异步处理 使用mod、crate和worksapce组织代码 feature的作用 技巧汇总

  • 安装、vscode插件
  • struct/enum数据结构
  • 模式匹配、错误跳转、错误处理、异步处理
  • 使用modcrateworksapce组织代码
  • feature的作用
  • 技巧汇总

开始

  1. 安装
export RUSTUP_DIST_SERVER=https://mirrors.sjtug.sjtu.edu.cn/rust-static
export RUSTUP_UPDATE_ROOT=https://mirrors.sjtug.sjtu.edu.cn/rust-static/rustup

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
  1. vscode的插件
  • rust-analyzer
  • rust syntax
  • crates:帮助你分析当前项目的依赖是否是最新的版本
  • better toml:高亮 toml 语法
  • rust test lens:可以帮你快速运行某个 Rust 测试
  • Tabnine:基于 AI 的自动补全
  • error lens: 显示错误
  1. cargo

Rust 使用名为 cargo 的工具来管理项目,做依赖管理以及开发过程中的任务管理,也是包管理工具

基础语法

函数类型

f: fn(i32) -> i32

数据结构

  • 常量
    • 常量是一个右值
    • 编译后放在数据段
  • 静态变量
    • 静态变量也放在数据段
    • 不是右值
  • sturct结构体
    • 空结构体:不占空间 struct Marker
    • 元组结构体:类似python的list:struct Color(u8, u8, u8)
    • 普通结构体:带名称
  • enum:枚举与联合
    • C枚举体

      enum G {
        a = 0, 
        b = 1, 
        c = 2
      }
      
    • rust的标签联合体

      enum Event {  
        Join((UserId, TopicId)),  
        Leave((UserId, TopicId)),  
        Message((UserId, TopicId, String)),}
      

struct 定义结构体,用 enum 定义标签联合体(tagged union)

  • CloneCopy
    • Copy / Clone 两个派生宏
    • Clone 让数据结构可以被复制
    • 而 Copy 则让数据结构可以在参数传递的时候自动按字节拷贝

模式匹配

  • 用于 struct / enum 中匹配部分或者全部内容
  • if/while let pat = expr {}match 的简写,用于只关心某种模式匹配的情况

错误跳转?

expr?:当错误发生时退出函数,并返回错误

异步处理

expr.await:执行async函数直到完成。当前上下文可能被阻塞多次 socket.write(data).await?;

错误处理

  • 借鉴 Haskell,把错误封装在 Result<T, E> 类型中
    • T是成功执行返回的结果类型
    • E代表错误类型
  • ?操作符传播错误
fn main() {
  // ...
  let body = reqwest::blocking::get(url).unwrap().text().unwrap();
}

unwrap()方法抽取成功的结果,如果出错则终止 要传播错误,把unwrap()换成?操作符,并让main()返回Result<T, E>

fn main() -> Result<(), Box<dyn std::error::Error>> {
  let body = reqwest::blocking::get(url)?.text()?;

  Ok(())
}

项目的组织

rust用mod组织代码

  • 引入包
    • 一个一个引入
      • 在项目的入口文件 lib.rs/main.rs 中,用 mod 声明要引入的文件
    • 一次引入目录下的所有文件
      • 把所有要引入的文件放在一个目录下
        • 在目录中创建 mod.rs
        • mod.rs 中引入其他文件
        • lib.rs/main.rs 中用 mod + 目录名

      mod

  • 一个项目称为一个 crate
  • 测试
    • 单元测试
      • 测试代码和代码放在相同文件中
      • 测试代码用条件编译#[cfg(test)]修饰

          #[cfg(test)]
          mod tests {
            #[test]
            fn it_works() {
              assert_eq!(2+2, 4);
            }
          }
        
    • 集成测试
      • 放在 tests 目录下
      • 只能测试 crate 的公开接口
      • 会编译成单独的可执行文件
      • 使用 cargo test 执行集成测试
  • workspace
    • 作用
      • crate 代码太多,任何修改会重新编译整个 crate
      • workspace 包含多个 crate
        • 修改代码时,只有涉及的 crate 会重新编译
    • 使用
      • 在目录下生成 Cargo.toml
      • 包含所有 crate

        workspace

feature

feature 用作条件编译,你可以根据需要选择使用库的某些 feature。它的好处是可以让编译出的二进制比较灵活,根据需要装入不同的功能。在 docs.rs 下的某个库的文档中,你可以看到它都有哪些 feature。

定义 feature,你可以看 cargo book:https://doc.rust-lang.org/cargo/reference/features.html。下面是一个简单的例子:

在 cargo.toml 中,可以定义:


[features]

filter = ["futures-util"] // 定义 filter feature,它有额外对 futures-util 的依赖。



[dependencies]

futures-util = { version = "0.3", optional = true } // 这个 dep 声明成 optional

在 lib.rs 中:

#[cfg(feature = "filter")]

pub mod filter_all; // 只有编译 feature filter 时,才引入 mod feature_all 编译

技巧

打印类型的大小

println!("sizeof Result<String, ()>: {}", size_of::<Result<String, ()>>());
sizeof Result<String, ()>: 24

优化思路应该是跟Option<T>类似。Result<String, ()>的false case是(),就相当于是Option<String>,可以用String里的ptr的值来实现零成本抽象

获取命令行参数

let args = std::env::args().collect::<Vec<String>>();

if let [_path, url, output, ..] = args.as_slice() {
  println!("url: {}, output: {}", url, output);
} else {
  println!("参数缺失");
}

运行工程

cargo run -- post httpbin.org/post a=1 b=2
  • -- 就相当于 target/debug/httpie

运行example

  • 配置

      [[example]]
      name = "exp"
      path = "example/exp.rs"
    
  • 代码

      // example/exp.rs
      fn main() {
          // 代码
      }
    
  • 运行

      cargo run --example exp
    

运行bin

  • 配置

      [[bin]]
      name = "main"
      path = "src/main.rs"
    
  • 代码

      // src/main.rs
      fn main() {
          // 代码
      }
    
  • 运行

      cargo run --bin main
    

web 相关知识的学习:

  1. HTTP 协议需要基本了解(请求和响应长什么样子,都有什么方法(GET, POST, PUT, DELETE, PATCH, HEAD 等),HTTP 头(比如 content-type),状态码,MIME 类型,如何做 content negotiation 等)

  2. Request / Response 模型需要了解

  3. 一般的 web 框架的路由结构,middleware 结构和并发处理模型需要了解 建议了解 http 协议后,简单看某个 web 框架的文档,比如 expressjs:https://expressjs.com/en/guide/routing.html

查看错误代码的详细说明

rustc --explain E0382

实现一个RawBuff类型

use std::{fmt, slice};

#[derive(Clone, Copy)]
struct RawBuf {
    ptr: *mut u8,
    len: usize,
}

impl From<Vec<u8>> for RawBuf {
    fn from(vec: Vec<u8>) -> Self {
        let slice = vec.into_boxed_slice();
        Self {
            len: slice.len(),
            ptr: Box::into_raw(slice) as *mut u8,
        }
    }
}

impl fmt::Debug for RawBuf {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let data = self.as_ref();
        write!(f, "{:p}: {:?}", self.ptr, data)
    }
}

impl AsRef<[u8]> for RawBuf {
    fn as_ref(&self) -> &[u8] {
        unsafe { slice::from_raw_parts(self.ptr, self.len) }
    }
}

fn main() {
    let data = vec![1, 2, 3];
    let buf: RawBuf = data.into();
    use_buf(buf);
    println!("buf: {:?}", buf);
}

fn use_buf(buf: RawBuf) {
    println!("buf to die: {:?}", buf);

    drop(buf);
}

impl Drop for RawBuf {
    fn drop(&mut self) {
        println!("drop");
    }
}

as_ref, as_ptr

  • as_ref拿到的是栈的指针
  • as_ptr拿到堆指针
  • Vec::into_boxed_slice(), 然后再用 Box::into_raw 拿到裸指针

不会越界的数组index

let len = self.len();
idx =  (len + idx % len) % len