人间熙攘,好久不见

vuePress-theme-reco Magic    2017 - 2023
人间熙攘,好久不见 人间熙攘,好久不见

Choose mode

  • dark
  • auto
  • light
Home
Category
  • tools
  • Zsh
  • iTerm2
  • Front-end
  • 版本控制-Git
  • Rust
  • skia-safe
  • 第三方对接
  • MQTT
  • Powershell
  • python
  • MD5
  • SHA1
  • wsl2
Tags
TimeLine
GitHub
  • Github (opens new window)
  • Before (opens new window)
author-avatar

Magic

23

文章

15

标签

Home
Category
  • tools
  • Zsh
  • iTerm2
  • Front-end
  • 版本控制-Git
  • Rust
  • skia-safe
  • 第三方对接
  • MQTT
  • Powershell
  • python
  • MD5
  • SHA1
  • wsl2
Tags
TimeLine
GitHub
  • Github (opens new window)
  • Before (opens new window)

Rust之初:初识

vuePress-theme-reco Magic    2017 - 2023

Rust之初:初识

Magic 2021-03-21 Rust
Rust Come on

rust-gif

# Rust 是什么?

套用官网的一句话:一门赋予每个人构建可靠且高效软件能力的语言。

它的语法类似 c++,但是 Rust 能更高效地提供许多功能来保证 性能 和 安全,而且 Rust 还能在无需使用传统的垃圾收集系统的情况下保证内存的安全性。

# 可以做什么?

来一张官网的截图:

rust-construct

可以看出来能做的事情之多。。。

# 安装

可以根据官网方法安装:Rsut (opens new window),mac 可以通过 brew 安装,w10 可以通过 scoop 安装。

# Rust是怎么管理内存安全的

上文说到 Rust 能在无需使用传统的垃圾收集系统的情况下保证内存的安全性,那它是通过什么来保证的呢?

# 所有权

什么是所有权?它是 rust 独有的管理系统(可以使 rust 无需垃圾回收就可以保证内存安全)。它是在编译时完成的,所以并不会带来任何运行时的成本。
rust 默认是在 栈上(已知且固定的大小) 分配内存的,如果需要存储大小未知或者大小可能变化的数据,就要改为存储到 堆 上(会返回位置指针)。

规则:

  • Rust 中的每一个值都有一个被称为其 所有者(owner)的变量
  • 值有且只有一个所有者
  • 当所有者(变量)离开作用域,这个值将被丢弃

查看以下代码:


 
 
 


 


fn main() {
  let str1 = "heelo";
  {
      let str2 = "测试";
  }
  // str2 不能使用,因为出了作用域,由编译器完成
  println!("s is {}, b is {}", str1, str2);
}
  • 再看一个例子:

 
 



 
 
 

fn main() {
    let x = Box::new(5); // 智能指针(储存在 堆上)详见 https://doc.rust-lang.org/book/ch15-01-box.html
    add_one(x);
    println!("{}", x);
}

fn add_one(mut num: Box<i32>) {
    *num += 1; // * 解引用
}

输出: rust-code2

在调用 add_one 方法时,变量 x 的所有权转移到了 num 上 (value moved here),当所有权转移时,可变性可以从不可变变成可变的(mut (opens new window)),函数完成后,num 的内存将自动释放(出了作用域,会被 drop 掉)。当 println! 再次使用已经没有所有权的变量 x 时,编译器就会报错(如上图所示)。可以尝试 add_one 返回一个 Box 数据,把所有权转移回来,更好的做法时 引入 借用。


 


 

 


let x = Box::new(5); // 智能指针(储存在 堆上)详见: https://doc.rust-lang.org/book/ch15-01-box.html
let x = add_one(x); // 隐藏(Shadowing)详见:https://kaisery.gitbooks.io/trpl-zh-cn/content/ch03-01-variables-and-mutability.html
println!("{}", x);

fn add_one(mut num: Box<i32>) -> Box<i32> {
    *num += 1;
    num
}

# 引入 与 借用

  • 引用:通过 & 使用;
  • 借用:通过 * 使用;
fn main() {
    let mut vec0 = Vec::new();
    vec0.push("2");

    let vec1 = fill_vec(vec0);

    println!("{} has leangth {}", vec0, vec1)
}

fn get_len(vec: Vec<&str>) -> usize {
    vec.len()
}

调用 get_len,传入变量 vec0,这个时候如果直接传入,运行后会报如下错误:

rust-code2

会告诉我们详细发生了什么(被移动了),就是说在 调用函数结束后 会释放内存,因为在 rust 中 vec0 被移动到了 vec 中,vec0 无效了 (所有权 (opens new window)),还记得之前 我们提到 离开 作用域 被丢弃了,函数完成 vec 就被清理了,所以 什么都没有了...

改动一下 代码如下:


 
 



 

...
let vec1 = fill_vec(vec0);
fn get_len(vec: &Vec<&str>) -> usize {
    vec.len()
}
...
// 输出:["2"] has leangth 1

借用:就是把 所有权 借用给了 vec,函数完成后,vec 又把 所有权还给了 vec0(这里的引用时不变引用,就是可以 读取 不能 修改,对应的 又 可变引用 &mut),所以运行成功了。

特别注意:

  • 在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。
  • 引用必须总是有效的。

# 生命周期:生命周期标记

rust 中生命周期基本跟其他语言类似:声明一个变量,生命周期开始,变量离开作用域,生命周期结束。

查看如下代码:

{
    let r;

    {
        let x = 5;
        r = &x;
    }

    println!("r: {}", r);
}

rust-code3
这段代码会编译失败,因为引用了一个 无效引用,变量 x 离开作用于了。

通过 rust 借用检查器(它比较作用域来确保所有的借用都是有效的),示例如下:




 
 
 
 




{
    let r;                // ---------+-- 'a
                          //          |
    {                     //          |
        let x = 5;        // -+-- 'b  |
        r = &x;           //  |       |
    }                     // -+       |
                          //          |
    println!("r: {}", r); //          |
}

可以看到 'b 没有 'a看起来长,就是代表 'b 的生命周期比 'a 的要小:被引用的对象比它的引用者存在的时间更短(悬垂引用)。 更改后代码如下:




 
 
 
 


{
    let x = 5;            // ----------+-- 'b
                          //           |
    let r = &x;           // --+-- 'a  |
                          //   |       |
    println!("r: {}", r); //   |       |
                          // --+       |
}

rust中生命周期重要的部分是 生命周期标记 : 通过 ' 撇号开头,名称基本都是小写:

&i32        // 引用
&'a i32     // 带有显式生命周期的引用
&'a mut i32 // 带有显式生命周期的可变引用

我们可以声明一个带有 生命周期标记的 结构体:

#[derive(Debug)]
struct Test<'c, 'd> {
    x: &'c String,
    y: &'d String,
}

fn main() {
    let a = &String::from("hello");
    let b = &String::from("world");

    let test = Test { x: a, y: b };

    println!("Test is {:?}", test);
}

rust-code3

当前结构体 Test 包含两个对 String 的引用,所以需要声明当前域生命周期注解,因为当前结构体有自己的 生存期,需要保证 引用 比当前 结构体 有更长的生命周期,否则就会编译失败,这样就会避免 悬垂引用 的问题。

再看一个例子(函数签名中的生命周期注解):

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
fn main() {
    let a = "hello";
    let result;
    {
        let b = String::from("world");
        result = longest(a, b.as_str());
    }
    println!("The longest string is {}", result);
}

rust-code3

可以看到报错信息:b does not live long enough,这是因为 result 生命周期 跟 main 一样长,但是 b 只有在花括号内才是有效的,但是出了花括号作用域就会被释放,所有就无效了。然后根据 longest 函数签名生命周期注解中表示 参数 x、y 的生命周期必须和返回值的 生命周期一样,所以 rust 在编译过程通过 借用检查器 判断出 b 的 生命周期不够长,所以就会报:b does not live long enough。

修改代码如下:

let b;
{
    ...
}
...

这样就保证:b 的生命周期和 a 的一样,就可以同归编译输出:The longest string is world。

所以 ”显示的指定” (Rust的设计哲学之一) 方便了编译器检查,但是对于 程序猿 有点 繁琐,但是为了安全考虑,我们就欣然接受吧~

欢迎来到 人间熙攘,好久不见
看板娘