【Rust1】基本语法



2022年08月27日    Author:Guofei

文章归类: 合集    文章编号: 11201

版权声明:本文作者是郭飞。转载随意,标明原文链接即可。本人邮箱
原文链接:https://www.guofei.site/2022/08/27/rust1.html


安装和使用

安装(具体参见 官网

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

更新

rustup update
cargo update

使用

# 编译
rustc main.rs

# 运行
./main

cargo:系统和包管理器

cargo --version

# 创建一个新的cargo项目
cargo new learn_rust_project

cargo init --lib

# 编译
cargo build

# 编译并运行代码
cargo run

# 编译并发布,
# 1)生成到 target/release 下,而不是 target/debug下
# 2)会做更深的优化
cargo build --release


# cargo 会优先搜索 cargo.lock 中的版本,以实现每次都用同一个版本的包。如果强制刷新 cargo.lock :
cargo update


# 下载所用的包的相关帮助,并打开
cargo doc --open

cargo clean

变量

// 可变变量
let mut x = 1;

// 不可变变量
let x = 1;

// 常量
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;

遮蔽

遮蔽

let x = 5;
// x = 5
let x = x + 1;
// x = 6

{
    let x = x * 2;
    // x = 12
    println!("The value of x in the inner scope is: {}", x);
}

println!("The value of x is: {}", x);
// 内部遮蔽结束,这里 x = 6

遮蔽2

// 可以重新绑定
let mut x = "abcd";
let x = "aaa";
let mut x = x.len();

数据类型

  • rust是静态类型,必须编译时知道所有变量的类型

整型

长度 有符号类型 无符号类型
8 位 i8 u8
16 位 i16 u16
32 位 i32 u32
64 位 i64 u64
128 位 i128 u128
根据系统,32/64位 isize usize
  • 浮点型 f32, f64
  • bool类型 bool
  • 元组
    • 赋值: let tup: (i32, f64, u8) = (500, 6.4, 1);
    • 可以是不同类型
    • 取值1:let (x, y, z) = tup;
    • 取值2: let y = tup.1;
  • 数组
    • 赋值 let a: [i32; 5] = [1, 2, 3, 4, 5];
    • 赋值2: let a = [3; 5]; 这是3重复5次
    • 赋值后长度固定,不可再改
    • 内存整块分配到栈上,而不是堆上
    • 取值:a[0]

其它

// 允许任意添加下划线,以提高可读性
let num: i32 = 100_00_000_000;

let num: i32 = 0xff; // 16进制
let num: i32 = 0o77; // 八进制
let num: i32 = 0b111_000;// 二进制

数据类型的选择

  • i32 大多数情况下最快
  • f64 与 f32 运行速度差不多,因此默认类型推导是 f64

format

let x = 42; // 42 is '2A' in hex

// 转十六进制
assert_eq!(format!("{x:X}"), "2A");
assert_eq!(format!("{x:#X}"), "0x2A");
assert_eq!(format!("{:X}", -16), "FFFFFFF0");

// n进制:b二进制,xX十六进制,o八进制
let a = format!("{:o}", 0b111000);
println!("{}", a);

// 转科学计数法
assert_eq!(format!("{x:E}"), "4.2E1");

// 引用、Box 和其它指针类型的地址
format!("{:p}", &a)

x.to_string() // 转文本


// +代表显示符号,#代表显示进制头,0代表补0,12代表最小长度,b代表二进制
let a = format!("{num:+#012b}", num = 0b110);
println!("{}", a);


// 1位小数
let a = format!("{a:8.1}", a = 1.23);
println!("{}", a);


// https://blog.csdn.net/feiyanaffection/article/details/125575203

as 转化

// bool 类型可以转 int
assert_eq!(true as i32, 1); // true转换为1

// char 类型其实是一个 i32
assert_eq!('*' as i32, 42);
// ascii 值转 char
assert_eq!(65 as char, 'A');
assert_eq!(std::char::from_u32(65).unwrap(), 'A');

// 数值类型可以任意相互转化

基本运算

+-*/ // 加减乘除
a%b //取余

数字对应的方法

// 按位前后颠倒
let a = 0b01110000_i8.reverse_bits(); // 返回 0b00001110
// swap_bytes:按字节颠倒
// 二进制表示的1的个数
let a: u32 = 0b1110_i8.count_ones();
// 二进制表示的0个个数
let a: u32 = 0b1110_i8.count_zeros();

a&b // 按位与
a|b // 按位或
a^b // 按位异或
!a // 按位否
>> // 按位右移
<<

逻辑

// 比较
==, !=, <, >, <=, >=
// 逻辑运算
&&
||
! // 逻辑非

科学计算

// 常量
use std::f64::consts::{PI, E, LOG10_E};


let a: f64 = 9.0;

a.abs()

a.exp(); // e^a
a.exp2(); // 2^a
a.log(3.0); // 以3.0为底的对数
a.log2(); // 以2为底
a.log10(); // 以10为底
a.ln()
x.ln_1p() // ln(1+x)

a.powf(3.0); //a^3.0
a.powi(3); //a^3



a.floor(); // 向下取整
a.ceil(); // 向上取整
a.round(); //四舍五入

a.fract(); // 小数部分
a.trunc(); // 整数部分

a.clamp(3.0, 5.0); // 裁剪到3.0~5.0之间


// 大端表示小端表示
// a.to_le,a.to_be,to_be_bytes,to_ne_bytes

https://doc.rust-lang.org/std/primitive.i32.html

// 含头不含尾
0..6
(0..6).rev()

结构体 struct

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

fn main() {
    // 不允许单独一个字段可变,只能整体全都可变
    let mut user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };

    // 这样改字段值
    user1.email = String::from("anotheremail@example.com");
}

简写1

let email = String::from("123@qq.com");
let username = Strin::from("name");

let mut user2 = User {
  // 简写:email:email,
    email,
    username,
    active: true,
    sign_in_count: 2,
};

简写2:复制别的值进来

let user3 = User {
    email: String::from("321@"),
    ..user1
};
// user1就不能用了,因为它的 username 已经移动过来了
// 不过 active 和 sign_in_count 还是可以用

没有字段名的结构体

struct Color(i32, i32, i32);
let black = Color(0, 0, 0);

// 可以声明字段共有
// struct Color(pub i32,pub i32,pub i32);

没有字段的结构体

struct AlwaysEqual;

fn main() {
    let subject = AlwaysEqual;
}

注:字段默认是私有的。

结构体的方法

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // &self 是 self: &Self 的缩写
    // 这里的 &self 指的是 rectangle: &Rectangle
    // 加 & 是因为不想获取所有权,只希望读,而不希望写
    fn area(&self) -> u32 {
        self.width * self.height;
    }
}

方法和字段可以同名

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn width(&self)->bool{
        self.width>0
    }
}

impl 也可以不接一个方法,通常用来构建一个对象

impl Rectangle {
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            height: size,
        }
    }
}

// 通常用来做一个构造器
impl Rectangle{
  pub fn new() -> Rectangle{
    Rectangle{
      width: 1,
      height: 1
    }
  }
}
// 用 Rectangle::new() 可以调用它

// Self指得是自身类的别名
impl Rectangle{
  pub fn new() -> Self{
    Self{
      width: 1,
      height: 1
    }
  }
}

泛型

泛型用于 struct

// 1. 派生 trait 。这要求所有的字段都支持这些 trait 才行
#[derive(Copy, Clone, Debug, PartialEq)]
struct Point<T> {
    x: T,
    y: T,
}

// 2. 泛型用于方法
impl<T> Point<T> {
    fn get_x(&self) -> &T {
        &self.x
    }
}

// 都合法
let p1 = Point { x: 5, y: 6 };
let p1 = Point { x: "mid", y: "mid" };

使用泛型时,如何做类型推断

pub struct MyQueue<T> {
    data: Vec<T>,
}

impl<T> MyQueue<T> {
    pub fn new() -> MyQueue<T> {
        MyQueue {
            data: Vec::new()
        }
    }

    pub fn push(&mut self, t: T) {
        self.data.push(t)
    }

    pub fn pop(&mut self) -> Option<T> {
        self.data.pop()
    }
}

fn main() {
    // 可以自动推断类型(最常用)
    let mut q1 = MyQueue::new();
    q1.push(1.0);

    // 也可以这样指定类型
    let mut q2 = MyQueue::<i32>::new();

    //也可以这样
    let mut q3: MyQueue<i64> = MyQueue::new();
}

知识点

  1. 泛型没有额外的性能消耗。编译时会把所有用过的具体类型都填进去。

生命周期:https://blog.csdn.net/feiyanaffection/article/details/125574590

match 相关

enum:枚举

enum IpAddrKind {
    // 可以是不同类型
    V4(u8, u8, u8, u8),
    V6(String),
}

let four = IpAddrKind::V4;

// IpAddrKind 可以像一个类型一样使用
let six: IpAddrKind = IpAddrKind::V6(String::from("::1"));

应用:

  1. 范型。例如在 vec中塞入int和string
  2. Option 类型。实际上是 enum
  3. Result 类型。实际上是 enum

match

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}
let coin = Coin::Penny;
let res: u8 = match coin {
    Coin::Penny => {
        println!("Lucky penny!");
        1
    }
    Coin::Nickel => 5,
    Coin::Dime => 10,
    Coin::Quarter => 25,
};

match 中的 other

fn func1() {}
fn func2(num_spaces: u8) {}

let num = 9;
match num {
    3 => func1(),
    7 => (), // 表示什么也不做
    other => func2(other)
}

如果变量 other 没用,可以用下划线代替

match num {
    3 => func1(),
    7 => func2(),
    _ => func1()
}

知识点

  • pub 枚举的所有构造式和字段都会是 pub 的
  • match 可以是 元组、结构体。
  • 可以多匹配 ` 9 10 => { println!(“big”) }`

https://blog.csdn.net/feiyanaffection/article/details/125574617?spm=1001.2014.3001.5502

泛型枚举 Option 和 Result

常见的泛型枚举:

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

enum Result<T, E> {
  Ok(T),
  Err(E)
}

如何使用 Option

// Option:
let opt = Some(5); // or None

// 1. 用 match
match opt {
    None => { println!("值为 None"); }
    Some(num) => { println!("值为 {}", num); }
}
// 1.1 if let 
// 1.2 let a = match{}

// 2: unwrap
// 是 None 时设定为某个值
let a: i32 = opt.unwrap_or(7);
// 是 None 时调用一个函数,返回的是 Option
let a: Option<i32> = opt.or_else(|| Some(6));
// 是 None 时返回默认值
opt.unwrap_or_default()


// 2.1 ok_or: 转 Result 
// ok_or:Option 转 Result
let a: Result<i32, &str> = opt.ok_or("hello");
// 然后用链式很方便
let a: i32 = opt.ok_or(anyhow!("hello"))?;
// ok_or_else(err_fn),类似于 ok_or 但是传入的是一个函数


// 3. 逻辑运算
// 如果 opt1 为 Some,返回 opt1;如果opt1 为 None,返回 opt2
opt1.or(opt2) 
// 如果两个都是 Some/None,返回 None;如果一个 Some,一个是 None,返回 Some
opt1.xor(opt2)


// 4. 其他消费方式
if some_option.is_some(){
    ...
}
if some_option.is_none(){
    ...
}

// 慎用
opt.unwrap() // 为 None 时 Panic
opt.unwrap_unchecked() // 必须用 unsafe 圈起来,程序员自己保证其非 None

// 5. 关于 Result 错误的链式传递,参见下面的章节“错误”

Result

// Result:
let mut res: Result<i32, String> = Ok(5);
// 1.1 match 
res = Err("错误信息".to_string());
match res {
    Err(e) => { println!("出错:{}", e); }
    Ok(num) => { println!("值为:{}", num); }
}

// 1.2 可以使用 let x = ... 来接受代码块中的信息
let x = match res {
    Ok(x) => {
        println!("Ok :x = {}", x);
        x
    }
    Err(e) => {
        println!("error:{}", e);
        0
    }
};

// 1.3 if let 语句
// 1.3.1 if let Err(e) 也可以
// 1.3.2 这个 x 的作用范围只在代码块里面
if let Ok(x) = res { 
    println!("Ok x = {}", x);
} else { println!("error") }
// 一个返回 Result的函数
fn func1(x: i32) -> Result<i32, String> {
    if x < 0 {
        Err(format!("错误信息: {} <0 ", x))
    } else {
        Ok(x)
    }
}

let res = func1(-6);
match res {
    Ok(T) => { println!("正确的值 {:?}", T) }
    Err(e) => { println!("错误的值,e:String = {:?}", e) }
}

如何解Result

// 1. 使用问号 ?,允许链式传递错误,简洁
fn my_main() -> Result<String, io::Error> {
    let mut s = String::new();
    File::open("a.txt")?.read_to_string(&mut s)?;
    Ok(s)
}
// 1.1 如果涉及到多个 Error 类型,可以把它们都转为 Result<T, String> 类型,做到统一。
fn my_main() -> Result<(), String> {
    let bytes = fs::read("不存在的文件").map_err(|e| e.to_string());
    let y = func1(-5)?;
    Ok(())
}
// 1.2 当然也可以不使用 String,而是自定义 struct Error
// (代码略)



// 2. unwrap_or_else: 允许更自定义的处理
// 2.1 给出一个默认值,如果错误就使用默认值
let bytes = fs::read("不存在的文件").unwrap_or_else(|e| vec![]);
// 2.2 或者 panic
let bytes = fs::read("不存在的文件").unwrap_or_else(|e| panic!("出错啦! {:?}", e));
// 2.3 或者按照 error 类型处理
let bytes = fs::read("不存在的文件").unwrap_or_else(
    |e| {
        if e.kind() == ErrorKind::NotFound { panic!("没找到文件,{}", e) } else { panic!("其它错误, {}", e) }
    });


// 4. 遇到错误直接 panic(慎用)
fs::read("不存在的文件").unwrap();
fs::read("不存在的文件").expect("遇到错误 panic");
fs::read("不存在的文件").expect_err("遇到正确 panic");

知识点

程序默认:Panic时,Rust沿着调用栈反方向遍历调用函数,并依次清理数据。也可以立即停止,这样就不会清理,内存只能由操作系统回收,如果想如此,就这样:

# Cargo.toml
[profile.release]
panic = 'abort'

Rust有两种错误

  • Panic:不应该有的错误
  • Result:这里

关于 Panic

// 1. 手动触发 Panic
panic!("panic");
// 2. 无意中触发 Panic
let num = 5 / 0;
// 3. 对 Result 类型使用 unwrap, expect。

控制流程


let y = {
    let x = 3;
    x + 1
};
// y=4,因为代码块最后一句是它的返回值

// 函数的最后一句也是返回值,但最后一句不要加分号
fn my_func(x: i32) -> i32 {
    x + 1
}

if 语句

// 1. 不会自动转换为 bool 值,必须手动转换
if x < 0 {
    println!("x<0")
} else if x < 1 {
    println!("0<=x<1")
} else { println!("x>=1") }

if 实现三目运算符

let age = 20;
// 每个分支必须都是相同类型
let sex = if age > 18 { "adult" } else { "child" };

loop:不停的执行

  1. 可以与break,continue连用
    let mut x = 1;
    loop {
     x = x + 1;
     if x > 20 {
         break;
     } else if x % 3 == 0 { continue; }
     println!("{}", x);
    }
    
  2. break 可以像 return 一样使用,然后赋值
    let mut cnt = 1;
    let res = loop {
     cnt += 1;
     if cnt > 10 {
         break "end";
     }
    };
    
  3. break可以指定一个 生命周期
    'search:  // 下面break的时候,直接跳到这里(跳了2层)
    for i in 0..10 {
     for j in 0..10 {
         println!("i = {}, j = {}", i, j);
         if j == 5 {
             break 'search;
         }
     }
    }
    

while 条件循环

while cnt < 10 {
    cnt += 1;
}

for 循环

let lst = [10, 20, 30, 40, 50];

for element in lst {
    println!("the value is: {}", element);
}


for num in (1..4).rev() {
    println!("{}", num);
}

匿名函数

let func_even = |x: u32| x % 2 == 0;
// 指定类型就必须加大括号
let func_even2 = |x: u32| -> bool{ x % 2 == 0 };

函数指针

fn add_one(x: i32) -> i32 {
    x + 1
}

fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
    f(arg) + f(arg)
}

fn main() {
    let answer = do_twice(add_one, 5);

    println!("The answer is: {}", answer);
}

所有权和生命周期

所有权让 Rust 无需 GC

内存管理的方式

  • 垃圾回收机制,不断寻找不再使用的内存,例如 Java
  • 开发者写明内存分配和释放,例如 C
  • Rust:所有权机制

所有权规则

  • 每一个值都有一个 owner (变量)
  • 值在任一时刻有且只有一个所有者。
  • 当owner(变量)离开作用域,这个值将被丢弃。

堆栈

  • 栈 Stack:
    • 存的数据大小固定
    • 后进先出,新数据总在栈顶
    • 不需要寻址
  • 堆 Heap
    • 堆内存上开辟地址,标记为已用,然后返回该地址的指针
    • 访问速度比Stack慢,因为经过了一次指针

clone和copy

  • clone() 拷贝堆和栈上的数据,特别消耗资源
  • copy() 只拷贝栈上的数据,需要类型实现 Copy Trait

作用域

{
    let a = 1;
}
// a 的作用域结束,这里失效了
println!("{}", a); //所以报错
let x = 5;
let y = x; // 这里发生了 Copy,所以可以正常运行
println!("{}{}", x, y);

// 但是这样就会报错,因为 s1 的数据被“移动”到s2了
let s1 = String::from("hello");
let s2 = s1;
println!("{}{}", s1, s2);

// 改成这样就可以了
let s1 = String::from("hello");
let s2 = s1.clone();
println!("{}{}", s1, s2);

如果类型实现了 copy trait,那么就可以用等号。哪些实现了 copy trait 呢:

  • 所有的整型、浮点型,例如 u32, f64
  • bool 类型
  • 字符类型 char
  • 元组,当且仅当其包含的类型也都实现 Copy 的时候。比如,(i32, i32) 实现了 Copy,但 (i32, String) 就没有。

哪些没有?

  • String
  • struct
  • 其它

所有权与函数

向函数传值的时候,等同于赋值,所以规则是同上的

fn main() {
    let s = String::from("hello");
    takes_ownership(s);
    // println!("{}", s); // 所有权已经没了,会报错
    let x = 5;
    makes_copy(x); // 因为是 copy 的,所以所有权还在
    println!("{}", x); // 不会报错
}

fn takes_ownership(some_string: String) {
    // 运行结束,some_string 调用了 drop 方法,内存释放
}

fn makes_copy(some_integer: i32) {
//     这里的some_integer 也会调用 drop 方法,但它是 copy 来的,对之前的值没有影响
}

另外,函数return一个值的时候,所有权转移出去了(因此不会drop掉)
(代码略)

  • 但不要 return 它的引用,因为对应的值会drop。进而导致这个引用指向一个无效的内存。Rust 编译器不通过

借用

上面这种情况,把一个 String 传入函数后,就失去所有权了。但是x1 往往不希望这种情况发生。所以有了借用的概念

fn main() {
    let s1 = String::from("hello");

    // &s1 创建了一个指向 s1 的引用,但没有其所有权。
    // 函数可以使用 s1,但不获取所有权
    let len = calculate_length(&s1);

    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
  // 因为没有所有权,所以不能更改。例如,不能 s.push_str("aaaa");
    s.len()
//     因为没有所有权,所以函数结束后不触发 drop
}

但是x2 有时候还想让它在函数中可变,这就需要使用 可变借用

fn main() {
    // 1. s1 要加 mut,因为 s1 是要改动的
    let mut s1 = String::from("hello");

    // 2. &mut 表示可变引用
    let len = calculate_length(&mut s1);

    println!("The length of '{}' is {}.", s1, len);
    // s1 变成了 'helloaaaa'
}

// 3. 声明函数的时候,也要明确加 &mut
fn calculate_length(s: &mut String) -> usize {
    // 就可以改 s 了
    s.push_str("aaaa");
    s.len()
}

但是x3,同一个值,同一时间,只能有一个可变引用。这是为了防止数据竞争(data race),它会导致程序结果不可知。

  • 一个引用的作用域:从声明开始,到最后一次使用结束
  • 可变引用和可变引用之间不能有生命周期重叠
  • 不可变引用和不可变引用之间可以有重叠
  • 可变引用和不可变引用不能有生命周期重叠
  • 总结来说,可变引用不能和任何引用重叠
let mut s = String::from("hello");

let r1 = &mut s;
let r2 = &mut s;

println!("{}, {}", r1, r2); // 报错,因为生命周期重叠了。
// 不可变引用的最后一次使用后,又声明了可变引用,就没有问题
let mut s = String::from("hello");

let r1 = &s; // 没问题
let r2 = &s; // 没问题
println!("{} and {}", r1, r2);
// 此位置之后 r1 和 r2 不再使用

let r3 = &mut s; // 没问题
println!("{}", r3);

slice

let mut s = String::from("hello world");
let s1_1: &str = &s[3..s.len()];
let s1_2 = &s[3..];
let mut s2_1 = &s[..];
println!("{},{},{}", s1_1, s1_2, s2_1);
  1. 切片等同指针
  2. 字符串slice相当于不可变引用。因此生命周期按照不可变引用来理解
  3. 字符串的字面值 &str,就是一个 slice

关于所有权的其它知识点

  1. 引用struct后,可以直接用 r.field,等价于 (*r).field
  2. 在做比较时,也无需解引用 r1<=r2;r1==r2,等价于 ` (rx) == (ry)`
  3. 但是 std::ptr::eq(rx, ry) 是判断引用是否指向同一块内存
  4. Rust的引用不可能为空
a.as_ref()

let a=&mut s;
let ref mut a = s;

所有权与 Vec

// 获取一个元素的引用,等同于获取整个 vec 的引用
let mut a: Vec<String> = vec!["1".to_string(), "2".to_string(), "3".to_string(), "4".to_string()];

let mut a1 = &mut a[1]; // 获取 整个 a 的可变引用
let mut a2 = &mut a[2]; // 又获取 整个 a 的可变引用
a1.push('c'); 
a2.push('c'); // 生命周期重叠,编译不通过

一些分离所有权的方法

let (left, right) = a.split_at_mut(idx); // 所有权分为两片,left 为 [0, mid),right 为 [mid ,len)
let item = a.remove(idx); // 把 item 拿出来,分离所有权,然后 shift, O(n) 复杂度
let item = a.swap_remove(idx); // 把 item 拿出来,分离所有权,然后把最后一个缓过来,O(1) 复杂度 

use std::mem;
let a2 = mem::replace(&mut a[2], "9".to_string()); // 把 a[2] 拿出来,分离所有权,原位置放入 "9".to_string()

生命周期

// 约定:输入的生命周期>=输出的生命周期
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        return x;
    }
    return y;
}


// late bound / early bound
// 'a: 'c 是指定 a 的生命周期比 c 长
fn longest<'a: 'c, 'b: 'c, 'c>(x: &'a str, y: &'b str) -> &'c str {
    if x.len() > y.len() {
        return x;
    }
    return y;
}

早限定和晚限定

fn f<'a>(inp:&'a str)->&'a str{inp} // late bound,实际调用时,才能确定具体的生命周期
fn g<'a:'a>(){} // early bound

// late bound,使用时候必须指定生命周期,否则报错
// let pf=f::<'static> as fn(); // 这个报错
let pf=f as fn(&'static str)->&'static str;

// early bound 则不用
let pg=g::<'static> as fn();
let pg=g as fn();


// println!("{}",pf==pg);

其它规则

  • 如果单入参,出参的生命周期与入参一样
  • 如果多入参,出参生命周期无法推断,需要手动定义
  • 如果是类方法,出参生命周期与self一致

静态变量

可变静态变量

static 和 const 是不同的

static mut COUNTER: u32 = 0;


fn add_one() {
    unsafe {
        COUNTER += 1;
    }
}

fn main() {
    add_one();
    // 读和写都必须在 unsafe 中
    unsafe {
        println!("{}", COUNTER);
    }
}

区别

  • const :存储的是数据本身。编译时直接内连,每次使用时重新分配栈内存,内存地址会变化。
  • static:存储的是内存地址。全局只分配一次内存,内存地址固定。

以下情况使用 static

  • 存储大量数据
  • 固定地址
  • 要求内部可变性

使用时 static 比 const 多了一次寻址操作

lazy_static

// lazy_static = "*"
#[macro_use]
extern crate lazy_static;

// use lazy_static;
use std::collections::HashMap;

lazy_static! {
    static ref PRIVILEGES: HashMap<&'static str, Vec<&'static str>> = {
        println!("生成静态变量");
        let mut map = HashMap::new();
        map.insert("James", vec!["user", "admin"]);
        map.insert("Jim", vec!["user"]);
        map
    };
}

fn show_access(name: &str) {
    let access = PRIVILEGES.get(name);
    println!("{}: {:?}", name, access);
}

fn main() {
    println!("开始运行");
    let access = PRIVILEGES.get("James");
    println!("James: {:?}", access);

    show_access("Jim");
}

智能指针

最简单的指针就是引用,它没有额外开销

智能指针是一些数据结构,类似指针但有额外的元数据和功能。

  • Box 用于在堆上分配值
  • Rc 允许多重所有权引用计数类型
  • Ref和RefMut 可以通过 RefCell 是运行时执行借用的类型,而不是编译时

智能指针一般有两个trait

  • Deref trait:使智能指针结构体的实力拥有与引用一致的行为
  • Drop trait:指向的数据可以自动被释放掉

Box

Box

  • 数据在堆上,元数据在栈上
  • 递归类型无法在编译时确定具体大小,因此需要用 Box

生成和释放

  • 底层使用类似 mallocfree 这样底层的内存分配器来分配和释放
  • 生成:先在堆上分配数据,然后从栈上复制到堆上,返回一个 Box<T> 对象,其元数据存放在栈上
  • 释放:
    • 时机:实例超出其作用域时(例如函数的结尾,或者该变量不再被使用)
    • 调用drop函数:先释放栈,后释放堆
let b = Box::new(5);
println!("b = {}", b)
// 这个代码出现递归,因此无法通过编译
// enum List {
//     Cons(i32, List),
//     Nil,
// }

// 改成这样
enum List {
    Cons(i32, Box<List>),
    Nil,
}
// 需要空间是i32和一个Box对应的指针,就打破了无限递归

use crate::List::{Cons, Nil};

fn main() {
    let list = Cons(1, Box::new(
        Cons(2, Box::new(
            Cons(3, Box::new(Nil))))));
}

Rc

Rc

  • 堆上的数据
  • 多重多有权
  • rc 通过不可变引用共享只读数据
  • RefCell 与 Rc 联合使用可以绕开不可变的限制
名字 持有者数量 可变性 线程
Box 1个 编译时检查可变+不可变  
Rc 多个 编译时检查不可变 只能用于单线程
RefCell 1个 运行时检查可变和不可变 只能用于单线程

一个带叉链表

use std::rc::Rc;

#[derive(PartialEq, Eq, Clone, Debug)]
pub struct ListNode {
    pub val: i32,
    pub next: Option<Rc<ListNode>>,
}

fn main() {
    let a = Rc::new(ListNode { val: 1, next: Some(Rc::new(ListNode { val: 2, next: None })) });
    println!("引用计数数量 = {}", Rc::strong_count(&a));

    let b = ListNode { val: 6, next: Some(Rc::clone(&a)) };
    println!("引用计数数量 = {}", Rc::strong_count(&a));

    let c = ListNode { val: -6, next: Some(Rc::clone(&a)) };
    println!("引用计数数量 = {}", Rc::strong_count(&a));
}

待添加:RefCell

unsafe

unsafe可以实现这些能力

  • 解引用裸指针。
  • 调用不安全的函数或方法。
  • 访问或修改可变的静态变量。
  • 实现不安全trait。

裸指针与引用、智能指针的区别在于:

  • 允许忽略借用规则,可以同时拥有指向同一个内存地址的可变和不可变指针,或者拥有指向同一个地址的多个可变指针。
  • 不能保证自己总是指向了有效的内存地址。•
  • 允许为空。
  • 没有实现任何自动清理机制。

裸指针

示例

let mut num = 5;

// 不可变裸指针
let r1 = &num as *const i32;
// 可变裸指针
let r2 = &mut num as *mut i32;


// 裸指针只能在 unsafe 里面引用
unsafe {
    // 生命周期重叠
    println!("r1 = {}", *r1);
    println!("r2 = {}", *r2);
}

这段代码试图用一个随意的内存地址来创建拥有10 000个元素的切片。

use std::slice;

let address = 0x01234usize;
let r = address as *mut i32;

let slice : &[i32] = unsafe {
    slice::from_raw_parts_mut(r, 10000)
};

调用别的语言时,无法检查其它语言的安全性,所以也要用 unsafe

extern "C" {
    fn abs(input: i32) -> i32;
}

unsafe {
    println!("Absolute value of -3 according to C: {}", abs(-3));
}

其它

  • 高级 trait:https://weread.qq.com/web/reader/d733256071eeeed9d7322fdk07e323f027707e1cd7dc674
  • 高级类型:https://weread.qq.com/web/reader/d733256071eeeed9d7322fdkda432420278da4fb5c6e9ad
  • 高级函数和闭包:https://weread.qq.com/web/reader/d733256071eeeed9d7322fdk4c5327a02794c56ff4ce24c
  • 宏:https://weread.qq.com/web/reader/d733256071eeeed9d7322fdka0a32dd027aa0a080f42962

参考

https://rustwiki.org/zh-CN/book/ch03-05-control-flow.html

系列文章:https://blog.csdn.net/feiyanaffection?type=blog


您的支持将鼓励我继续创作!