5

Rust那些难理解的点(持续更新)

 3 years ago
source link: https://zhuanlan.zhihu.com/p/360342782
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Rust那些难理解的点(持续更新)

一个人NB的不是标签

Rust被连续五年评为stack overflow最喜欢的编程语言,声称没有undefined行为,以及没有data race。于是我花了时间学习看看是否真的那么神奇。在这个过程中,我发现还是那句老话”没有银弹“,Rust有适用的场景并且值得学习,但是并不是适用于所有的场景。本文记录学习的过程中,我觉得比较难以掌握的点。

Rust不是万能的

没有语言是万能,可以禁止一切bug。所以我们首先要清楚哪些bug是Rust防不住,哪些是防的住。

以下是Rust程序可以出现的bugs:

  • dead lock
  • logic bug
  • memory leak

以下是Rust程序在safe域里面不可能出现的bug

  • data race
  • undefined behavior (如use-after-free, dangling-pointer)

在unsafe域里面一切皆有可能。因为真正的程序总会有各种困难的问题,不可能通过语言层面解决。但是Rust通过区分safe和unsafe将问题进行隔离。程序员大部分时候使用safe Rust调用经过千锤百炼的unsafe code。这样犯错的概率会变小。

下面是一些难以理解,需要多花时间熟悉和练习才能掌握的知识点。

Rust独有的东西不多,生命周期是其中一个。(Owership借用的C++的RAII)

生命周期其实可以理解为类型。当你声明一个变量的时候,它全部的类型信息包括普通的类型,还有生命周期。比如下面的代码是通不过编译的。

let a: &usize;
{
  let b: usize = 5;
  a = &b;
}
println!("{}", a);

报的错误是

   Compiling playground v0.0.1 (/playground)
error[E0597]: `b` does not live long enough
  --> src/main.rs:8:7
   |
8  |   a = &b;
   |       ^^ borrowed value does not live long enough
9  | }
   | - `b` dropped here while still borrowed
10 | println!("{}", a);
   |                - borrow later used here

error: aborting due to previous error

因为a的类型是&'a usize,但是&b的类型是&'b usize,其中'a的生命周期长于'b,因为b所在的作用域更小。可以理解生命周期是跟作用域挂钩的。生命周期的引入是为了防止

  • use-after-free
  • dangling-pointer

因为Rust只允许长的生命周期引用赋予给更短或者一样的生命周期变量。如果一个引用它持有的变量生命周期肯定比它长或者相等。

这个简单的例子,理解起来没有困难。难一点的是在结构体和函数中的生命周期如何去理解。

pub struct Person<'a, 'b> {
  pub name: &'a str,
  pub age: &'b usize,
}
let name = "Hell";
let age = 18;
let p = Person { name: &name, age: &age};

这里的'a和'b,表示这个结构体是一个关于生命周期的泛型结构体。我们可以创建不同的Person,这些Person里面的name和age具有不同的生命周期,就像如下我们对类型T进行泛型一样

pub struct P<T> {
   pub name: T
}
//我们可以创建 P<usize> 以及P<String>

当我们创建Person结构体,我们会将'a和'b这两个生命周期具体化,在这里就是name和age的生命周期。也可以认为,结构体p不能存活比name和age变量更长(因为它持有的引用是name和age)。也就是下面的代码会报错

let p :Person;
{ 
  let name = "Hell";
  let age = 18;
  p = Person { name: &name, age: &age};
}
println!("{}", p.name);

我们在Impl 方法的时候也会遇到生命周期,比如

impl<'a, 'b> Person<'a, 'b> {
  pub fn name(&self) -> &str {
    self.name
  }
}

通常我们写代码不会出现'a和‘b,而这里出现,感觉又没有用到,直接去掉会报错

   Compiling playground v0.0.1 (/playground)
error[E0726]: implicit elided lifetime not allowed here
 --> src/main.rs:8:6
  |
8 | impl Person {
  |      ^^^^^^- help: indicate the anonymous lifetimes: `<'_, '_>`

error: aborting due to previous error

如果我们按照报错,加上<'_, ’_>可以通过编译,但是语义有发生了变化。这里的视频例子讲解了其中的区别 https://www.youtube.com/watch?v=rAl-9HwD858

实际上我们阅读源代码的时候,关注的是语义,会忽略许多细节。生命周期我们会忽略掉,因为我们并不是在找bug而是在看别人是如何实现一些东西。

如果从C++过来,类比Rust的引用于C++的引用,发现两者差距很大。比如C++的里面的引用声明以后就可以直接当成原来的值/变量进行使用,但是在Rust里面,我们需要解引用(方法会有自动解引用的语法糖)。所以,Rust里面的引用更像C++里面的指针。Rust赋予引用生命周期的概念,正如前文所讲的。

可变性与不可变

可变变量与可变引用交织在一起。可变变量是可以改变绑定的值的变量。(Rust默认一切变量不可变,也就是不可更改绑定,不可修改)。

可变引用是对一个值的引用。请看例子

let mut r = "hello";
let reference: & mut;

r和reference不一样,但是又有着联系。其中当你想改变一个值本身的时候,你需要获取这个值的可变引用(&mut)。而你只能对一个可以改变绑定的值(也就是可变变量)获取可变引用。比如

let r = String::from("hello");
r.push('c');

会报错如下

error[E0596]: cannot borrow `r` as mutable, as it is not declared as mutable
  --> src/main.rs:32:1
   |
31 |     let r = String::from("hello");
   |         - help: consider changing this to be mutable: `mut r`
32 | r.push('c');
   | ^ cannot borrow as mutable

error: aborting due to previous error

这是因为r不是可变变量,而String的push方法的签名是

pub fn push(&mut self, ch: char)

它需要获取一个self(也就是要修改的String)的可变引用。所以我们需要将r变成可变变量,即通过在r前面添加mut,程序就可以通过编译

let mut r = String::from("hello"); 
r.push('c');

而在r前面添加了mut会将r变成可变变量,也就是可以更改绑定的变量,比如

let mut r = String::from("hello");
r = String::from("world"); // r 重新绑定到了新的String

所以,你只能通过一个可变变量获取可变引用,你不能通过一个不可变变量获取可变引用。可变变量的是这个变量可以更改绑定。

以下是,我觉得对学习Rust有帮助的文章和视频。

陈皓老师有一篇文章写了Rust,《Rust语言的编程范式

Manish的系列文章,比如Prolonging Temporaries in Rust

https://www.youtube.com/watch?v=rAl-9HwD858


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK