Rust the Note Book

发布于 13 天前  47 次阅读


0x0 引

这里是最为魔怔的2020宅家春节。不想写操作系统也不想刷PWN题,那就趁此机会好好提升一下弱鸡的编程能力吧。

怎么说呢,似乎对C++有着与生俱来的抗拒感,但又觉得现代化编译型语言咋说也得选一门儿。这样一筛选,似乎可选项少的可怜。

既然自己喜欢的技术是二进制漏洞,那Rust所保证的安全性便是十分的吸引我了。的确是比较好奇它是如何在编译器层面解决了C与C++的那些,作为自由的代价的安全问题的。

最初着手研究时,发现这门新兴语言的中文社区与资料是真的少呀。对着圈子内较为有名的《Rust编程之道》与《深入浅出Rust》看了几天,深感索然无味,几近劝退。耐不住寂寞知乎搜索了“Rust”、“学习”。

轮子哥有玩笑话,“零基础掌握Rust需要五年”。所幸有答主推荐了官方教程《Rust the Book》(圣经)给我以勇气。

此前从不青睐官方文档的我这次切实感觉到了官方文档的亲和力。手把手的体验很赞。只是苦于 the Book 仅有英文版本,也不能中意于 Google 君的翻译。不过英文文档阅读效率不理想也说不过去。受用一生的能力还是趁早修炼为好。

现在的感受是rustc实乃一个事无巨细的保姆级编译器.一个会教你如何编写代码,如何修改bug,甚至如何命名符号的编译器。

——《Rust the Note Book》,源自《Rust the Book》,IZAYOI御用。

0x1 语言基础概念


0x1.1 变量与可变性

fn main() {
    let x = 5;          // Rust使用let关键字进行变量绑定
    let y : i32 = 6;    // 吸取了C++等语言的历史遗留缺陷的教训,使用了后置标识变量类型
                        // 省略类型时,Rust编译器会尝试自动判定变量类型
    // x = 7;              编译失败。line2的方式绑定为不可变变量
    let mut x = 7;      // 变量覆盖, line2的x被覆盖
    x = 8;              // 编译通过。mut x为一个模式,绑定为可变变量
    
    const CONST_VALUE : u32 = 10_000;   // 使用const关键字声明常量
}

0x1.2 数据类型

元类型

整型
LengthSignedUnsigned
8-biti8u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
archisizeusize
整型进制
Number literalsExample
Decimal98_222
Hex0xff
Octal0o77
Binary0b1111_0000
Byte (u8 only)b'A'
浮点型
fn main() {
    let x = 2.0; // 默认为f64
    let y: f32 = 3.0; // f32
}
布尔型
fn main() {
    let t = true; // 布尔型占用空间为1Byte
    let f: bool = false;
}
字符型
fn main() {
    let c = 'z';
    let heart_eyed_cat = '😻'; // Rust的char类型由Unicode编码,一个char占用4Bytes
}

复合类型

元组
fn main() {
    let tup = (500, 6.4, 1);                    // 声明方式1,元组中的元素可以是不同类型
    let tup : (i32, f64, u8) = (500, 6.4, 1);   // 声明方式2
    let (x, y, z) = tup;                        // 取值方式1
    let v_500 = tup.0;    let v_6_4 = tup.1;    let v_1 = tup.2; // 取值方式2
}
数组
fn main() {    
  let months = ["January", "February", "March", "April", "May", "June", "July",
                  "August", "September", "October", "November", "December"];
    // 数组中的元素必须是相同数据类型的 
    let a = [1, 2, 3, 4, 5];                // 声明方式1
    let a = [3; 5];                         // 声明方式2,等效于let a = [3, 3, 3, 3, 3]; 
    let a : [i32; 5] = [1, 2, 3, 4, 5];     // 声明方式3

    let one = a[0];                         // 使用下标取值
}

0x1.3 函数,语句,表达式

fn main() {
    println!("iFunc returns {}", iFunc(5, 6));
}

// fn 函数名(参数1: 类型, 参数2: 类型 ......) -> 返回类型 { 函数体 }
fn iFunc(x: i32, y: i32) -> i32 {           // 声明位置在调用位置之后也是没有问题的说
    let iVar = {
        println!("Assign a value to iVar"); // 这是一个语句,其值为空,以()空元组表示
        x - 1
    };  

    if x < 3 {
        return x + 1;   // 使用return提前返回
    }

    {                   // {}内为一个代码块,以其为单位约束作用域.整体返回一个值
        let b = 4;
        y + b - 1       // 末尾没有分号,这是一个表达式,其值为计算结果.在此处成为了iFunc的返回值    
    }
}

0x1.4 流程控制

fn main() {

    // if else
    let iNum = 3;

    if iNum % 4 == 0 {      // if后的表达式值类型必须为布尔型
        println!("iNum is divisible by 4");
    } else if iNum % 3 == 0 {
        println!("iNum is divisible by 3");
    } else {
        println!("iNum is not divisible by 4, 3");
    }


    // if let
    let condition = true;

    let iNum = if condition {
        5
    } else {
        6                   // 使用if let进行变量绑定时,需保证if else后接的表达式的类型相同
    };


    // loop
    let mut iNum = 0;

    let result = loop {
        iNum += 1;
        if iNum == 10 {
            break iNum * 2; // break--循环代码块专享的return.后接返回值
        }
    };

    // while
    let mut iNum = 5;

    while iNum != 0 {       // 同if,此处表达式值类型须为布尔型
        println!("{}", iNum);
        iNum -= 1;
    }

    // for
    let a = [10, 20, 30, 40, 50];

    for element in a.iter() { // 被遍历的数据类型须已实现了Iterator的trait
        println!("the value is: {}", element);
    }

    // match
    let x = 3;

    let out = match x {               // match 块中每一分支情况的返回类型必须相同
        1 => {
            println!("one");
            x
        },
        2 => x-1,
        3 => x-2,
        _ => x,             // “_”为通配符,代表了余下情况
                            // match 块必须保证x的所有取值情况被考虑在内,故此处“_”不能丢
    };

}

0x2 所有权


0x2.1 基本概念

"所有权机制是rust最为独特的特性"——the Book讲的。

所有权机制使得rust在摒弃了GC的情况下能够保证内存安全。这算的上是rust的一大杀手级优势了,同时却也是一大劝退特性。经由我个人浅薄的经验来说,对于底层堆栈内存管理有一定的了解的话会很有助于理解所有权机制的工作方式。

其核心概念如下

  • 程序中的每一个值归于一个变量所有(正如每一个物品资源只有它唯一的主人),称此变量为此值的所有者
  • 当所有者离开作用域时(阳寿已尽),其所持有的值将被从内存中删除(私人物品陪葬作罢)

这种自动销毁与陪葬机制有效解决了困扰C于C++多年的悬空指针与UAF安全漏洞。此处不是旨在讨论二进制安全的地方,便不再展开讨论。如下便是所有权机制的具体语法规则的叙述了。

rust的作用域单位由{}所约束的代码块划分,接下来以String类型为例来继续说明所有权机制:

fn main() {
        // { 前iStr不可用,因其尚未声明
    {   

        let iStr = String::from("deadbeef"); // iStr便是"deadbeef"的所有者
        // 此区域可尽情享用iStr

    }   // 右花弧处会自动调用drop函数以返还失去所有者的数据所占用的内存
        // } 后iStr不可用,因其超出作用域
}

0x2.2 移动与拷贝

变量之间赋值时的内存操作策略

fn main() {
    // Copy,拷贝,栈上同时存在x, y变量,且值都为5
    let x = 5;
    let y = x;

    // Move,移动,"deadbeef" String对象所占用的堆内存的所有者由s1变为了s2,s1此后不再生效
    let s1 = String::from("deadbeef");
    let s2 = s1;                        

    let s3 = s2.clone();    // 深拷贝s1得到s2,堆内存拷贝一份,栈上变量s1, s2共存
}

采用Copy的数据类型:

  • 整型
  • 布尔型
  • 浮点型
  • 字符型
  • 仅包含以上类型的元组

0x2.3 所有权与函数

函数调用可以带走所有权

fn main() {
    let x = 5;
    makes_copy(x);
    // 由于x的值被Copy了一份,所以此行位置x依然有效

    let s = String::from("deadbeef");
    takes_ownership(s);
    // 由于"deadbeef" String对象的所有权被传递给了takes_ownership()函数,所以此行位置变量s无效
}

fn makes_copy(num: i32) {
    println!("{}", num);
}

fn takes_ownership(str: String) {
    println!("{}", str);
}

函数返回值可以返还所有权

fn main() {
    let s1 = gives_ownership();         // s1获得了函数内部的String对象的所有权

    let s2 = String::from("deadbeef");

    let s3 = takes_and_gives_back(s2);  // s2对于"deadbeef" String对象的所有权被函数带走又返还给了s3
}

fn gives_ownership() -> String {
    let str = String::from("deadbeef");
    str
}

fn takes_and_gives_back(str: String) -> String {
    str
}

0x2.4 引用

仅仅是想要借用一下变量的值的话,0x2.3中的将值据为己有后使用,用完返还的方式不尽如人意。引用便是解决这个问题的更为简洁的一种“借用”手段。借来其值而不拿走值的所有权。一个引用型的变量本质上是一个指向原数据的指针。

fn main() {
    let mut iStr = String::from("deadbeef");    // 全程所有者都是iStr

    borrow(&iStr);                  // 传递一个引用

    borrow_and_change(&mut iStr);   // 传递一个可变引用
}

fn borrow(str: &String) -> usize {  // 接受引用为参数
    str.len()
}

fn borrow_and_change(str: &mut String) {    // 接受可变引用为参数
    str.push_str("!");
}

可变应用有一个规则:在同一时间同一变量你只能拥有一个可变引用或多个不可变应用。由此限制了写数据的入口同时只能有一个,从而遏制了条件竞争漏洞的发生。


0x2.5 切片

切片是一种特殊的引用,它亦不夺走所有权而只是借用值。

fn main() {
    let iStr = "deadbeef";
    let iArr = [1, 2, 3, 4, 5];

    let s1 = &iStr[1..4];   //  ead
    let s2 = &iStr[..=4];   // deadb
    let s3 = &iStr[..];     // deadbeef
    let a1 = &iArr[1..3];   // [2, 3, 4]
}

0x3 结构体


0x3.1 结构体形式

具名结构体

fn main() {
    let mut user1 = User {
        email: String::from("z1906826229@gmail.com"),
        username: String::from("IZAYOI"),
        active: true,       // 为字段赋值的顺序可变
        sign_in_count: 1   
    };

    let user2 = User {
        email: String::from("475245666@qq.com"),
        username: String::from("TurboGT 6.0"),
        ..user1             // 利用已有的user1快速赋值
    };

    user1.email = String::from("1906826229@qq.com");    // 修改字段值
}

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool          
}

fn build_user(email: String, username: String) -> User {
    User {
        email,      // 当形参与字段同名时,可以省略字段值将形参值赋予字段
        username,   // 此处等效于 username: username
        active: true,
        sign_in_count: 1
    }
}

元组结构体

fn main() {
    let Red = RGB(0xff, 0x00, 0x00);
    assert_eq!(Red.0, 0xff); assert_eq!(Red.1, 0x00); assert_eq!(Red.2, 0);
}

struct RGB(u32, u32, u32);

单元结构体


0x3.2 结构体方法

fn main(){
    let iRect = Rectangle { width: 50, height: 30 };
    println!("iRect's area is {}", iRect.area());   // 方法使用 . 调用

    let iSqua = Rectangle::square(40);              // 关联函数使用 :: 调用
    println!("iSqua looks like {:?}", iSqua);       // {:?} 用于打印调试信息
}

#[derive(Debug)]                // 允许打印此结构体的调试信息
struct Rectangle {
    width: u32,
    height: u32
}

impl Rectangle {                // 使用impl关键字添加方法与关联函数
    fn area(&self) -> u32 {     // 为Rectangle实例添加一个方法,方法的参数中必须包含&self参数
        self.width * self.height
    }
}

impl Rectangle {                // 可以使用多个impl块来继续添加方法与关联函数
    fn square(size: u32) -> Rectangle {     // 为结构体本身添加一个关联函数
        Rectangle { width: size, height: size }
    }
}

0x4 枚举

0x4.1 定义枚举

fn main() {
    let m = Message::Move { x: 1, y:2 };
}

enum Message {
    Quit,                       // 不带有任何数据的变体
    Move { x: i32, y: i32 },    // 带有匿名结构体的变体
    Write(String),              // 可见枚举中的变体可以直接携带数据
    Color(u32, u32, u32),
}

0x4.2 enum Option<T>

由标准库定义如下

enum Option<T> {
    Some(T),
    None,
}

这一枚举被引入标准库的目的是解决由NULL空值带来的相关漏洞。rust中并不存在空值这一值,但在实际的编程中却常常是需要空值这一概念的。Option枚举便是rust给出的解决方案。NULL由Option<T>::None所替代。非空的值则存放于Some(T)变体之中。

fn main() {
    let x = Some(5);            // Option<T>随标准库默认已引入,所以Option::前缀可省略
    let y : Option<i32> = None; // 由于无法从空值中推断泛型类型,所以需要在使用None时显式标注
}

0x5 常用数据集合


0x5.1 向量Vec

fn main() {
    let mut v: Vec<i32> = Vec::new(); // 使用new()创建
    let mut v = vec![1, 2, 3];        // 使用宏创建

    v.push(5);
    v.push(6);

    let three = v[2];       // 拷贝
    let three = &v[2];      // 引用
    let three = v.get(2).unwrap();   // 调用方法

    for i in &v {           // 对向量的迭代
        println!("{}", i);
    }

    // 借助枚举获得能存放不同类型数据的向量
    enum SpreadsheetCell {  // 定义在函数内也是没有问题的说
        Int(i32),
        Float(f64),
        Text(String),
    }

    let row = vec![
        SpreadsheetCell::Int(3),
        SpreadsheetCell::Text(String::from("blue")),
        SpreadsheetCell::Float(10.12),
    ];
}

0x5.2 字符串

fn main() {
    let s1 = String::from("dead");  // 创建并初始化

    let mut s2 = String::new();     // 仅创建
    s2.push_str("beef");            // 追加字符串

    let mut s3 = s1 + &s2;          // s1的所有权转移至此
    s3.push('!');                   // 追加字符

    let s = format!("0x{}", s3);    // format宏,返回格式化后的String

    let chinese = String::from("呀嘞呀嘞哒啧"); // 此处每个汉字的Unicode占用3Bytes 
    let len = chinese.len();        // len为18,len()返回的是字节数
    let part1 = &chinese[..6];      // 切片单位为Byte,得到字符串“呀嘞”
    let part2 = &chinese[1..3];     // 未能截取到完整Unicode字符,引发panic
}

0x6 泛型

fn main() {

    let f_i_point = Point{ x: 5.1, y: 10 };
    let i_f_point: Point<i32, f64> = Point{ x: 5, y: 10.1 };

    let iX = f_i_point.x();

    let iY = i_f_point.y();

    let iOK: Output<i32, u64> = Output::OK(5);      // 由于编译时要确定大小,故需指明所有泛型类型
    let iFail: Output<u8, String> = Output::Fail(String::from("Error!"));
}

struct Point<C, u> {        // 结构体中的泛型
    x: C,                   // 类型代号可自定义,约定俗成为T
    y: u, 
}

impl<T, E> Point<T, E> {    // 方法中的泛型,impl后的<>让编译器知道Point<T, E>中的T, E为泛型而非具体类型
    fn x(self) -> T {
        self.x
    }
}

impl<i32, f64> Point<i32, f64> {    // 实现一个Point<i32, f64>专用的方法
    fn y(self) -> f64 {
        self.y
    }
}

enum Output<T, E> {     // 枚举中的泛型
    OK(T),
    Fail(E),
}

0x7 特性

0x8 生命周期