当前位置:首页 > 科技  > 软件

Rust 上手很难?搞懂这些知识,前端开发能快速成为 Rust 高手

来源: 责编: 时间:2024-05-23 08:32:59 84观看
导读在我的交流群里有许多人在讨论 rust。所以陆续有人开始尝试学习 rust,不过大家的一致共识就是:rust 上手很困难。当然,这样的共识在网上也普遍存在。这篇文章,就是专门为想要学习 rust 的前端开发而写,为大家抛开 rust 的

Edb28资讯网——每日最新资讯28at.com

在我的交流群里有许多人在讨论 rust。所以陆续有人开始尝试学习 rust,不过大家的一致共识就是:rust 上手很困难。当然,这样的共识在网上也普遍存在。Edb28资讯网——每日最新资讯28at.com

这篇文章,就是专门为想要学习 rust 的前端开发而写,为大家抛开 rust 的迷雾,让大家感受到,上手 rust,其实没有那么难。从本质上来说,他跟 JavaScript 是非常相似的。大家可以将这篇文章作为 rust 学习的先导片,我将会提前为大家扫清那些阻碍你学习 rust 的障碍,极大的降低 rust 的上手成本。Edb28资讯网——每日最新资讯28at.com

一、明确区分变量与值

JavaScript 并没有模糊变量与值的概念。然而由于许多人在学习 JavaScript 之初就没有重视变量与值的区别,在表达或者理解时,也经常混用,反正也不会出错,于是久而久之,就形成了刻板印象,变量与值就傻傻分不清了。Edb28资讯网——每日最新资讯28at.com

一定要记住,变量就是变量,值就是值。Edb28资讯网——每日最新资讯28at.com

// a 是变量// 2 是值// a = 2 是给变量赋值let a = 2;

在 rust 中,我们就必须要明确变量与值的区别,因为 rust 有一个非常有趣且核心的规定:每一个值,同时只能拥有一个变量。例如,如下代码中,我首先声明了一个变量 a,并且给 a 赋值一个字符串。Edb28资讯网——每日最新资讯28at.com

然后我声明一个变量 b,并将变量 a 赋值给 b。Edb28资讯网——每日最新资讯28at.com

let a = "123".to_string();let b = a;println!("xxxx, {}", a);// error: borrow of moved value: `a` value borrowed here after move

再然后,当我想要使用变量 a 时,我们发现报错了。Edb28资讯网——每日最新资讯28at.com

Edb28资讯网——每日最新资讯28at.com

根据我们刚才的那个规定,b = a 是将其值的所有权,转移给了 b,所以此时变量 a 失去了值。当我们再次想要通过变量 a 访问对应的值时,自然就会出错。Edb28资讯网——每日最新资讯28at.com

这个规定,在 rust 中,称之为所有权,是 rust 独特的核心设计,也是我们学习 rust 必须掌握的核心知识点之一。明确区分变量与值,能够帮我们快速掌握 rust 的这个核心特性。Edb28资讯网——每日最新资讯28at.com

二、重视可变与不可变

只有面试过大量的人,你才知道,好多人其实不知道 JavaScript 的基础数据类型是不可变的。对可变与不可变概念的不重视,也是导致前端上手 rust 困难的重要因素之一。Edb28资讯网——每日最新资讯28at.com

在 JavaScript 中,由于其强大的自动垃圾回收机制,我们在代码上可以随时修改变量的值,因此下面这段代码再正常不过了。Edb28资讯网——每日最新资讯28at.com

let a = 10;a = 20;

然而在 rust 中,由于没有垃圾回收机制,编译器必须明确知道变量到底是可变的还是不可变的,因此同样的代码,在 rust 中会直接报错Edb28资讯网——每日最新资讯28at.com

注意:我们这里说的是变量的可变性和不可变性,而不是值的可变性与不可变性。Edb28资讯网——每日最新资讯28at.com

let a = 10;a = 20;// error: cannot mutate immutable variable `a`

与此同时,如果你要声明一个具有可变性的变量,那么你需要通过语法明确的告诉编译器,这样这段代码就能编译通过。Edb28资讯网——每日最新资讯28at.com

// 即使这样写,编译器也会告诉你,你声明了一个值,// 但是这个值还没有被 read 过,就被重写了let mut a = 10;a = 20;

复杂的数据类型也保持了一样的规定。不加 mut 的情况下声明的变量,都是不可变的。Edb28资讯网——每日最新资讯28at.com

// 不加 mut 表示不可变,后续修改就会报错let mut p = Person {  name: "TOM".to_string(),  age: 32};p.name = "TOM2".to_string();

在 rust 的开发中,我们需要明确告诉编译器变量的可变与不可变,习惯了这一点,rust 的学习就进展了一大步。Edb28资讯网——每日最新资讯28at.com

// 这样表示不可变let a = 10;// 添加 mut 表示可变let mut a = 10;

三、纠正对于基础数据类型的认知

在我们前端开发中,有一个存在非常广泛的共识性知识的错误理解:那就是Edb28资讯网——每日最新资讯28at.com

基础数据类型存储在栈内存中Edb28资讯网——每日最新资讯28at.com

我在《JavaScript 核心进阶》中,专门花费了很多篇幅来讲解为什么这是一个错误的理解。不过,很显然,对于前端开发而言,这个知识的理解是否正确,并不重要,因为他不影响我们的代码逻辑和功能实现。因此大家都不够重视。Edb28资讯网——每日最新资讯28at.com

然而在 rust 中,对于这个知识的理解就显得尤其重要,当你带着这个错误理解来到 rust 的学习,你会感受到非常的不适应。Edb28资讯网——每日最新资讯28at.com

这里的关键之一,就在于字符串。Edb28资讯网——每日最新资讯28at.com

在 JavaScript 中,字符串是一个基础数据类型。但往往我们只会在栈内存中存储一些简单的数据,很显然,字符串可以变得复杂和庞大,庞大到整个栈内存可能都放不下。因此,字符串,其实并没有那么简单。Edb28资讯网——每日最新资讯28at.com

在 rust 中,字符串还原了他的本色,它是一个复杂数据类型,它存在于堆内存中。而与之对应的基本类型,变成了 char,表示单个字符。因此,我们需要非常严肃的对待字符串,把他看成一个复杂类型去学习。Edb28资讯网——每日最新资讯28at.com

// 声明一个字符串let hello: String = String::from("hello world!");// 声明一个字符串片段let name: &str = "TOM";// 将字符串片段转成字符串类型let name1: String = "TOM".to_string();// 将字符串转成字符串片段let name2: &str = hello.as_str();// 一个字符let a: char = 'h';

四 、精确理解引用类型

纯前端开发者对引用这个概念的理解有点大概差不多就是这样的意思。所以对于按值传递、按引用传递这样的概念理解得不是很透彻。当然,由于 JavaScript 太强大了,精准理解这些概念也没有太大的必要。Edb28资讯网——每日最新资讯28at.com

但在 rust 中,就必须要求开发者非常明确的搞懂按值访问/传递和按引用访问/传递。Edb28资讯网——每日最新资讯28at.com

首先,在 JavaScript 中的基本数据类型,总是按值访问/传递。 其原因是因为基本类型在内存中有明确的大小,非常的轻量,因此复制成本非常低,甚至有可能比复制一个引用的成本都还要低。Edb28资讯网——每日最新资讯28at.com

例如如下代码:Edb28资讯网——每日最新资讯28at.com

let a = 1;let b = a;b++;console.log(a); // 仍然为1console.log(b); // 变成了2

这段代码在内存中的表现如下图所示:Edb28资讯网——每日最新资讯28at.com

Edb28资讯网——每日最新资讯28at.com

在 rust 中,基本类型也有同样的表现。只不过我们要明确告诉编译器,变量 b 是一个可变变量。Edb28资讯网——每日最新资讯28at.com

let a = 1;let mut b = a;b += 1;println!(" {a:?}"); // 仍然为1println!(" {b:?}"); // 变成了2

在 rust 中基本类型虽然也可以有引用的写法 let b = &a;,但是为了降低理解成本,我们可以在初学时无视他,因为大多数场景也不会这样使用,就算使用了他的结果也没啥大的区别。Edb28资讯网——每日最新资讯28at.com

将基本类型传入函数中,也是一样,对于前端开发者来说,他不会发生什么灵异事件让我们理解不了。Edb28资讯网——每日最新资讯28at.com

// 简写语法:return v + 1fn addone(v: i32) -> i32 {  v + 1}let a = 10;let b = addone(a);println!("xxxx, : {}, {}", a, b);// xxxx, : 10, 11

我们声明了一个不可变变量 a,并将其传入函数 addone 中,此时 a 的值发生一次复制行为,并将复制之后的结果参与到函数的运行中去。因此最终 a 的值不受到函数执行的影响。这里的表现与 JS 一模一样。Edb28资讯网——每日最新资讯28at.com

其次,在 JavaScript 中的引用数据类型,总是按引用访问/传递。Edb28资讯网——每日最新资讯28at.com

例如下面这个例子,我声明了两个变量指向同一个值,当我通过任意一个变量引用修改值之后,最终的表现是两个变量都会发生变化。Edb28资讯网——每日最新资讯28at.com

const book = {  title: 'JavaScript 核心进阶',  author: '这波能反杀',  date: '2020.08.02'}const b2 = book;b2.author = '反杀';console.log(book); // {title: "JavaScript 核心进阶", author: "反杀", date: "2020.08.02"}console.log(b2);   // {title: "JavaScript 核心进阶", author: "反杀", date: "2020.08.02"}

这段代码在内存中的表现为:Edb28资讯网——每日最新资讯28at.com

Edb28资讯网——每日最新资讯28at.com

但是,类似的代码,在 rust 中就会出大问题。为什么呢,因为在 rust 中,默认是按照按值访问/传递。查看如下代码。Edb28资讯网——每日最新资讯28at.com

我需要一个可变的变量 b2,然后通过修改 b2 的值,来观察 book 的变化。Edb28资讯网——每日最新资讯28at.com

struct Book {  title: String,  author: String,  date: String}  let book = Book {  title: "rust 核心进阶".to_string(),  author: "这波能反杀".to_string(),  date: "2024.03.12".to_string(),};let mut b2 = book;b2.author = "反杀".to_string();println!("bookxxxx: {}", book.title);// error: borrow of moved value: `book` value borrowed here after move

是的,在 rust 中执行这段代码会报错,因为 rust 默认是按值访问,所以当我们在代码中执行 let mut b2 = book; 时,实际上已经将 book 对应的值的所有权,转移给了 b2。Edb28资讯网——每日最新资讯28at.com

所有权:每个值只能同时拥有一个变量。Edb28资讯网——每日最新资讯28at.com

此时,当我们再访问 book,编译器就会告诉我们,book 的所有权已经被转移了。Edb28资讯网——每日最新资讯28at.com

因此,如果我们要模仿出来 JavaScript 那种一样的代码,我们就需要借助引用来完成。Edb28资讯网——每日最新资讯28at.com

首先我们要约定好,book 的值是可变的。因此要使用 mut 来标识变量。Edb28资讯网——每日最新资讯28at.com

let mut book = Book {  title: "rust 核心进阶".to_string(),  author: "这波能反杀".to_string(),  date: "2024.03.12".to_string(),};

其次,对于 b2 来说,所有权不能被 b2 剥夺,因此我们需要使用引用。Edb28资讯网——每日最新资讯28at.com

// 赋值一份引用,表示借用:而不是所有权转移let b2 = &book;

但是,b2 也需要被修改,因此 b2 得是一个可变引用。Edb28资讯网——每日最新资讯28at.com

let b2 = &mut book;

完整代码如下:Edb28资讯网——每日最新资讯28at.com

struct Book {  title: String,  author: String,  date: String}  let mut book = Book {  title: "rust 核心进阶".to_string(),  author: "这波能反杀".to_string(),  date: "2024.03.12".to_string(),};let b2 = &mut book;b2.author = "反杀".to_string();println!("bookxxxx: {}", book.author);

在函数传参时也是这样的逻辑。因为 rust 是默认的按值传递,因此当我们将一个复合类型传入函数时,实际上是把值传进入,这样就会发生所有权的转移。Edb28资讯网——每日最新资讯28at.com

例如我声明一个简单的函数,然后只是在函数内部访问传入的值。Edb28资讯网——每日最新资讯28at.com

fn foo(bk: Book) {  println!("bookxxxx: {}", bk.author);}

然后执行该函数,当我们将 book 传入函数之后,再访问 book,就会发现报错,明确的告诉我们 book 已经失去值的所有权了。Edb28资讯网——每日最新资讯28at.com

let book = Book {  title: "rust 核心进阶".to_string(),  author: "这波能反杀".to_string(),  date: "2024.03.12".to_string(),};foo(book);// 报错println!("bookxxxx: {}", book.author);

为了确保 book 不会失去所有权,我们可以改造成按引用传递的方式。类型约束中,加上 &。Edb28资讯网——每日最新资讯28at.com

fn foo(bk: &Book) {  println!("bookxxxx: {}", bk.author);}

然后传入引用类型。Edb28资讯网——每日最新资讯28at.com

foo(&book);

这样,就跟 JavaScript 中的执行表现完全一致了。当然,我们如果要进一步在函数内部修改值,则传入可变引用即可。Edb28资讯网——每日最新资讯28at.com

fn foo(bk: &mut Book) {  println!("bookxxxx: {}", bk.author);}foo(&mut book);

ok,理解了这点小差异,基于 JavaScript 掌握 rust,可以说是信手拈来,毫无压力。Edb28资讯网——每日最新资讯28at.com

实践中,这种传入可变引用的场景其实是比较少的,按照函数式的指导思想来说的话,我们也应该尽量避免这样使用。Edb28资讯网——每日最新资讯28at.com

五、诡异的生命周期

按值传递时,内存往往更可控。因此,当我们总是在使用按值传递时,其实不会涉及到太过于复杂的生命周期的概念,编译器就能很轻松识别出来内存应该在什么时候回收。Edb28资讯网——每日最新资讯28at.com

但是,当我们使用引用时,情况就变得复杂起来。例如我们声明一个结构体。Edb28资讯网——每日最新资讯28at.com

struct Book2 {  title: &str,  author: &str,  date: &str}

该结构体三个字段都约定用引用类型来初始化。那么这个时候就有可能会发生一种情况:当我使用引用类型初始化该结构体时,有可能某一个字段的引用所对应的值,被提前销毁掉了,那该结构体该如何自处呢?例如这个例子。Edb28资讯网——每日最新资讯28at.com

// 声明一个标准字符串类型let title = String::from("rust 核心进阶");let book = Book2 {  title: title.as_str(),  ...}// 按值传递,title 失去值的所有权read(title);fn read(book: String) {  println!("xxxxx, {}", book);}

此时尴尬的事情就发生了,title 的值没了,所以呢,book.title 就访问不到值了。这种情况,被称为悬垂指针。Edb28资讯网——每日最新资讯28at.com

为了避免这种奇怪的事情发生,因此我们在使用引用时,就必须要明确的告诉编译器,我们到底会不会搞这种骚操作,让悬垂指针的情况出现。Edb28资讯网——每日最新资讯28at.com

约定的方式很简单,我们可以明确告诉编译器,结构体实例本身,与初始化的几个值,一定会拥有共同的生命周期。不会出现某个值的引用私自额外处理掉的情况。因此,我们会传入一个生命周期泛型,来完成我们这个约定。Edb28资讯网——每日最新资讯28at.com

struct Book2<'a> {  title: &'a str,  author: &'a str,  date: &'a str}

如果暂时不懂泛型,可以等懂了泛型再来回顾,这里的 'a 是随便写的一个字母,表达一个与泛型变量类似的概念,也可以是 'b,大家保持一致即可。Edb28资讯网——每日最新资讯28at.com

这里表达的是,Book2 的实例,与每一个初始化的引用,一定有相同的生命周期,大家会一起共进退。Edb28资讯网——每日最新资讯28at.com

约定了一致的生命周期之后,如果某个字段引用想要私自转移所有权,对不起,这种情况编译器就不会允许发生。Edb28资讯网——每日最新资讯28at.com

// 报错:cannot move out of `title` because....read(title);

在函数中也是一样,当我们要返回引用数据类型时,很多时候就需要标明生命周期,告诉编译器我们的约定。Edb28资讯网——每日最新资讯28at.com

例如这个案例,函数执行最终会返回入参中的一个,那么入参的生命周期与返回引用的生命周期就应该保持一致。因此我们使用泛型生命周期的语法约定一下即可。Edb28资讯网——每日最新资讯28at.com

fn longest<'b>(x: &'b str, y: &'b str) -> &'b str {  if x.len() > y.len() {    x  } else {    y  }}

如果不一致呢?我们就可以约定两个泛型生命周期变量。Edb28资讯网——每日最新资讯28at.com

fn longest2<'a, 'b>(x: &'a str, y: &'a str) -> &'b str {  let he = "hello";  let wo = "world";  if x.len() > y.len() {    he  } else {    wo  }}

在一些编译器能够推断出来的场景,就可以不需要约定生命周期。例如:Edb28资讯网——每日最新资讯28at.com

fn foo(x: &str) -> &str {  x}

除此之外,当你想要标识一个引用具有全局生命周期时,我们使用 'static。Edb28资讯网——每日最新资讯28at.com

let s: &'static str = "I have a static lifetime.";

rust 中的生命周期其实就这么简单。我们也有一种方式可以避免使用生命周期:那就是少使用引用。这个就很重要。Edb28资讯网——每日最新资讯28at.com

当然,有的时候我们还需要结合生命周期与泛型共同使用。看上去代码就很难懂。不过不要慌。把生命周期当成一个泛型变量就好了。Edb28资讯网——每日最新资讯28at.com

fn longest_with_an_announcement<'a, T>(  x: &'a str,  y: &'a str,  ann: T) -> &'a str where   T: std::fmt::Display{  println!("xxxx T: {}", ann);  if x.len() > y.len() {    x  } else {    y  }}

where 表示对 T 的类型进行进一步解释说明,明确限定 T 的使用范围。Edb28资讯网——每日最新资讯28at.com

// 表示将会在 {} 中使用变量where   T: std::fmt::Display

六、其他

还有一些 rust 的特性我并没有列出来,因为他们中的许多知识理解起来就没有太多的困扰性了,例如 trait、impl、数组、元组、enum、HashMap、mod、其他基础语法等。Edb28资讯网——每日最新资讯28at.com

当然,要成为 rust 高手,我们必须对栈内存和堆内存有非常准确的掌握,而不是仅仅只局限于知道一个概念。rust 要求我们对内存与数据类型有更精准的掌握。Edb28资讯网——每日最新资讯28at.com

除此之外,rust 与 JavaScript 一样,也是一门函数式编程语言。Edb28资讯网——每日最新资讯28at.com

rust 也用 let 与 const 声明变量与常量。这该死的亲切感。Edb28资讯网——每日最新资讯28at.com

rust 中也闭包。而且 rust 的闭包是显示出来的,理解起来更容易。当然,由于概念上引入了所有权、可变、不可变,所以导致了许多朋友在学习 rust 闭包时也充满了困惑,但是我们上面已经拿捏了这些概念,他们造成的难度都是纸老虎。Edb28资讯网——每日最新资讯28at.com

rust 的异步编程,有一个最常用的模式:单线程模型,与我们常说的事件循环体系是一模一样的。遗憾的是,许多前端对事件循环掌握得并不好,依然处于一个大概知道有这么个东西的阶段。Edb28资讯网——每日最新资讯28at.com

rust 也支持泛型,而泛型是 TS 的核心特性之一。rust 也有完善的类型推导机制,所以学习思路和 TS 都是一样的,关键的问题是,TS 的泛型和类型推导,反而更加灵活与复杂。Edb28资讯网——每日最新资讯28at.com

本文链接://www.dmpip.com//www.dmpip.com/showinfo-26-90187-0.htmlRust 上手很难?搞懂这些知识,前端开发能快速成为 Rust 高手

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 我们一起聊聊.NET快速实现网页数据抓取

下一篇: Redis大Key问题的深入探索与解决方案

标签:
  • 热门焦点
  • 一加Ace2 Pro官宣:普及16G内存 引领24G

    一加Ace2 Pro官宣:普及16G内存 引领24G

    一加官方今天继续为本月发布的新机一加Ace2 Pro带来预热,公布了内存方面的信息。“淘汰 8GB ,12GB 起步,16GB 普及,24GB 引领,还有呢?#一加Ace2Pro#,2023 年 8 月,敬请期待。”同时
  • Redmi Pad评测:红米充满野心的一次尝试

    Redmi Pad评测:红米充满野心的一次尝试

    从Note系列到K系列,从蓝牙耳机到笔记本电脑,红米不知不觉之间也已经形成了自己颇有竞争力的产品体系,在中端和次旗舰市场上甚至要比小米新机的表现来得更好,正所谓“大丈夫生居
  • 6月iOS设备好评榜:第一蝉联榜首近一年

    6月iOS设备好评榜:第一蝉联榜首近一年

    作为安兔兔各种榜单里变化最小的那个,2023年6月的iOS好评榜和上个月相比没有任何排名上的变化,仅仅是部分设备好评率的下降,长年累月的用户评价和逐渐退出市场的老款机器让这
  • 为什么你不应该使用Div作为可点击元素

    为什么你不应该使用Div作为可点击元素

    按钮是为任何网络应用程序提供交互性的最常见方式。但我们经常倾向于使用其他HTML元素,如 div span 等作为 clickable 元素。但通过这样做,我们错过了许多内置浏览器的功能。
  • 花7万退货退款无门:谁在纵容淘宝珠宝商家造假?

    花7万退货退款无门:谁在纵容淘宝珠宝商家造假?

    来源:极点商业作者:杨铭在淘宝购买珠宝玉石后,因为保证金不够赔付,店铺关闭,退货退款难、维权无门的比比皆是。&ldquo;提供相关产品鉴定证书,支持全国复检,可以30天无理由退换货。&
  • 7月4日见!iQOO 11S官宣:“鸡血版”骁龙8 Gen2+200W快充加持

    7月4日见!iQOO 11S官宣:“鸡血版”骁龙8 Gen2+200W快充加持

    上半年已接近尾声,截至目前各大品牌旗下的顶级旗舰都已悉数亮相,而下半年即将推出的顶级旗舰已经成为了数码圈爆料的主流,其中就包括全新的iQOO 11S系
  • OPPO K11搭载长寿版100W超级闪充:26分钟充满100%

    OPPO K11搭载长寿版100W超级闪充:26分钟充满100%

    据此前官方宣布,OPPO将于7月25日也就是今天下午14:30举办新品发布会,届时全新的OPPO K11将正式与大家见面,将主打旗舰影像,和同档位竞品相比,其最大的卖
  • 2022爆款:ROG魔霸6 冰川散热系统持续护航

    2022爆款:ROG魔霸6 冰川散热系统持续护航

    喜逢开学季,各大商家开始推出自己的新产品,进行打折促销活动。对于忠实的端游爱好者来说,能够拥有一款梦寐以求的笔记本电脑是一件十分开心的事。但是现在的
  • 亲历马斯克血洗Twitter,硅谷的苦日子在后头

    亲历马斯克血洗Twitter,硅谷的苦日子在后头

    文/刘哲铭  编辑/李薇  马斯克再次挥下裁员大刀。  美国时间11月14日,Twitter约4400名外包员工遭解雇,此次被解雇的员工的主要工作为内容审核等。此前,T
Top
Baidu
map