前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Rust入门之严谨如你

Rust入门之严谨如你

原创
作者头像
Radar3
修改于 2020-11-25 12:29:28
修改于 2020-11-25 12:29:28
1.8K00
代码可运行
举报
文章被收录于专栏:巫山跬步巫山跬步
运行总次数:0
代码可运行

1,简介

Rust作为一门快速发展的编程语言,已经在很多知名项目中使用,如firecracker、libra、tikv,包括WindowsLinux都在考虑Rust【1】。其中很重要的因素便是它的安全性和性能,这方面特性使Rust非常适合于系统编程。

团队近期的一个新项目对于“资源占用”、“安全稳定”有较严格的要求,因此团队调研并最终采用了Rust作为该项目的编程语言。

本文将演示一些很常见的编译器报错,这些信息对于Rust初学者似乎有些“不可理喻”,但当你熟悉之后再回头看,原来一切是这么理所应当。

有一种夸张的说法:“if the code compiles, it works.”【2】,反映了Rust将更多Bug发现于编译阶段的能力。下面让我们一起领略。

2,变量声明与使用

2.1,默认不可变

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
fn immutable_var() {
    let x = 42;
    x = 65;
}

   这段代码在多数编程语言是再正常不过的,但在Rust,你会看到如下编译错误:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
error[E0384]: cannot assign twice to immutable variable `x`
 --> src\main.rs:7:5
  |
6 |     let x = 42;
  |         -
  |         |
  |         first assignment to `x`
  |         help: make this binding mutable: `mut x`
7 |     x = 65;
  |     ^^^^^^ cannot assign twice to immutable variable

   编译器的提示已经非常友好,如果你需要一个可变变量,请在声明变量时显式添加mut关键字。

2.2,使用之前必须初始化

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
fn only_declare() {
    let x: i32;
    if true {
        x = 42;
    } else {
        ;
    }
    println!("x: {}", x);
}

   如果你的if分支比较多,在某个分支可能忘记给变量赋值,这将会引发一个Bug,而Rust会把这个Bug扼杀在编译阶段:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
error[E0381]: borrow of possibly-uninitialized variable: `x`
  --> src\main.rs:20:23
   |
20 |     println!("x: {}", x);
   |                       ^ use of possibly-uninitialized `x`

2.3,默认move语义

C++11开始引入move语义,它可以在变量转移时避免内存拷贝,从而提升性能,是C++11的一个重要特性,但是它需要显式使用或在特定场景自动适用。

而Rust更进一步,在非基本类型场景自动适用move语义:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
fn move_var() {
    let x = String::from("42");
    let y = x; //move occurred
    println!("x: {:?}", x);
}

  x的所有权被move到y中,x将失效,即:不允许再被使用。可以看到如下报错:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
error[E0382]: borrow of moved value: `x`
  --> src\main.rs:29:25
   |
27 |     let x = String::from("42");
   |         - move occurs because `x` has type `std::string::String`, which does not implement the `Copy` trait
28 |     let y = x; //move occurred
   |             - value moved here
29 |     println!("x: {:?}", x);
   |                         ^ value borrowed here after move

 3,所有权

所有权Ownership,这个概念我们在上一小节实际已经开始涉及到,所有权是Rust语言最为独特的一种机制和特性,Rust的“内存安全”很大程度正是依靠所有权机制。

值得注意的是,所有权的所有检查工作,均发生于编译阶段,所以它在运行时没有带来任何额外成本。

3.1,use of moved value

让我们回头看上一小节move_var例子,x在let y = x;之后,x原先的所有权已经转移给y,如果再使用x,就会报使用了一个已经被move走的值。

“42”这个字符串的值,实际是在堆区;x这个String对象内部保存有一个指向“42”的指针。

当move发生时,“42”这个堆区内存没有发生过拷贝,发生变化的只是y的栈指针指向了“42”这个堆地址,因此它是高效快速的。如果堆区内存非常大时,这种move的效率提升会更加明显。

3.2,借用默认不可变

借用Borrow,也就是C++里的引用,但它的默认可变性与C++不一样,这是Rust保守严谨的典型体现。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
fn borrow_var() {
    let v = vec![1, 2, 4];
    immu_borrow(&v);
    println!("v[1]:{}", v[1]);
}

fn immu_borrow(v: &Vec<i32>) {
    v.pop();
}

 上述引用使用方式,在C++是很常见的,我们看看Rust的报错信息:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
error[E0596]: cannot borrow `*v` as mutable, as it is behind a `&` reference
  --> src\main.rs:40:5
   |
39 | fn immu_borrow(v: &Vec<i32>) {
   |                   --------- help: consider changing this to be a mutable reference: `&mut std::vec::Vec<i32>`
40 |     v.pop();
   |     ^ `v` is a `&` reference, so the data it refers to cannot be borrowed as mutable

 如果需要可变借用,应该显式使用:&mut,这与变量声明是类似的。

3.3,不能同时有两个可变借用

为了避免产生数据竞争,Rust直接在编译阶段禁止了两个可变借用的同时存在(不用担心,并发有其他安全的办法实现),先看这段代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
fn mut_borrow_var_twice() {
    let mut v = vec![1, 2, 4];
    let x = &mut v;
    v[1] += 42;
    (*x)[1] += 42;
    println!("v[1]:{}", v[1]);
}

  会产生如下报错:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
error[E0499]: cannot borrow `v` as mutable more than once at a time
  --> src\main.rs:46:5
   |
45 |     let x = &mut v;
   |             ------ first mutable borrow occurs here
46 |     v[1] += 42;
   |     ^ second mutable borrow occurs here
47 |     (*x)[1] += 42;
   |     ---- first borrow later used here

  x是第一个可变借用,v是第二个可变借用,两个发生了交叉,编译器出于“担心你没有意识到代码交叉使用可变借用”,报出该错误。因为46行改值可能影响你原先对47行及其后的预期。

事实上,如果可变借用不是交叉,编译器会放行,比如:交换46、47行的两次借用。具体可以自行编译试一下。

3.4,不能同时有可变借用与不可变借用

下面将展示Rust更严格的一面,不仅不能同时出现两个不可变借用,可变与不可变借用也不能交叉出现,本质还是编译器“担心程序员没有注意到发生了交叉使用”,从而潜在产生Bug。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
fn mut_immut_borrow_var() {
    let mut v = vec![1, 2, 4];
    let x = &v;
    v[1] += 42;
    println!("v[1]:{}", x[1]);
}

  报错如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
  --> src\main.rs:54:5
   |
53 |     let x = &v;
   |             -- immutable borrow occurs here
54 |     v[1] += 42;
   |     ^ mutable borrow occurs here
55 |     println!("v[1]:{}", x[1]);
   |                         - immutable borrow later used here

  同上一小节一样,如果交换53、54行,编译器会放行。

3.5,严谨性不能覆盖的一面

前面两节介绍了编译器对于同时有两个借用的合法性检查,现在我们看一个同时有两个可变借用,但编译器无法覆盖的情况。【5】

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
fn two_mut_ref_compile_ok() {
    let mut v = vec![1, 2, 3];
    mut1(&mut v);
    mut2(&mut v);
}

fn mut1(v: &mut Vec<i32>) {
    *v = vec![0];
}

fn mut2(v: &mut Vec<i32>) {
    println!("{}", v[1]);
}

 这段代码编译是ok的,但是执行的话会报:thread 'main' panicked at 'index out of bounds: the len is 1 but the index is 1', src\main.rs:96:20。即数组索引越界,由此可见:可变借用的检查范围仅限于同一作用域内。

3.6,借用的有效性

引用失效会产生类似“悬空指针”的效果,在C++里是undefined behavior,而Rust会把这种问题拦截在编译阶段:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
fn dangle_ref() {
    let x: &i32;
    {
        let y = 42;
        x = &y;
    }
    println!("x:{}", x);
}

  严谨如Rust,它发现了你在使用一个悬垂引用,报错如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
error[E0597]: `y` does not live long enough
  --> src\main.rs:62:13
   |
62 |         x = &y;
   |             ^^ borrowed value does not live long enough
63 |     }
   |     - `y` dropped here while still borrowed
64 |     println!("x:{}", x);
   |                      - borrow later used here

  如果你把64行注释掉,即:不在失效后继续使用失效引用,则编译器予以放行。

到这里其实已经涉及到“生命周期lifetime”的概念,这是Rust又一特色特性,在其他语言里也有类似生命周期、作用域的概念,但是Rust的生命周期更加高级、复杂,却也让Rust更加安全、保守,本文作为一篇入门暂不深入涉及它。

4,内存安全

4.1,非法内存使用

C++对程序员没有限制,一个指针可以指向任何地方,当你对一个野指针解引用,在C++会产生undefined behavior,而Rust不建议这样的事情发生:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
fn invalid_mem_use() {
    let x = 42 as *mut i32;
    *x = 65;
}

  报错如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
error[E0133]: dereference of raw pointer is unsafe and requires unsafe function or block
  --> src\main.rs:69:5
   |
69 |     *x = 65;
   |     ^^^^^^^ dereference of raw pointer
   |
   = note: raw pointers may be NULL, dangling or unaligned; they can violate aliasing rules and cause data races: all of these are undefined behavior

 报错提示使用unsafe函数包裹这段代码,这里涉及到“unsafe”的概念。

由于Rust默认是保守的,如果在部分场景下程序员能够对代码负责,而Rust无法确认该代码是否安全,这时可以用unsafe关键字包住这段代码,提示编译器这里可以对部分检查进行放行。

但是unsafe并不代表这段代码不安全或存在内存问题【3】,unsafe一个常见的使用场景是通过libc进行系统调用。

4.2,空指针

空指针的发明者对于这个发明无比懊悔【4】,Rust没有历史包袱,它没有空指针。但是Rust依靠枚举和类型检查,提供了一个安全的空指针功能。先来看Rust标准库提供的这个名为Option的类型:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
enum Option<T> {
  None,
  Some(T),
}

  T是模板类型,Option可以是None或Some二选一,如果是Some的话可以带一个T类型的值。

即None代表空,Some代表非空,值是T。

比如你有一个A类型,你不直接操作A的对象a,你操作的是Option<A>类型的对象x。

如果你想调用a.f(),你必须先判断x是一个None还是Some,在Some分支内才可以拿到a去操作a.f()。而这一切都在Rust编译器的检视能力之内。任何能通过编译的代码,都没有机会在None上调用f()。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct A {}
impl A {
    fn f(&self) {}
}
fn safe_null() {
    let x: Option<A> = None;
    match x {
        Some(a) => a.f(),
        None => (),
    }
}

  如此巧妙地避开了空指针问题!

5,其他

1,Rust不会因为你永远没有调用到某些代码,而不去对其中的代码进行编译检查。比如本文的所有实例,都没有被main调用,却被进行了编译检查。

2,使用他人提供的库时,认值阅读函数原型,根据第一个入参是&self、&mut self还是self来决定你的使用方式,self意味着move语义。

如果你不注意,一定会遇见一个编译报错,不要慌,按照”编译器驱动“的开发模式来即可,编译器多数时候甚至会提示你正确的写法是什么。

3,Rust还有智能指针、channel、trait、包管理、闭包、协程等现代化编程语言标配功能,逐个学习,祝你早日打开新世界的大门!

6,参考

【1】https://www.oschina.net/news/109553/rust-for-linux-kernel

【2】https://doc.rust-lang.org/book/ch20-02-multithreaded.html?highlight=it,compiles,it,works#building-the-threadpool-struct-using-compiler-driven-development

【3】https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#unsafe-rust

【4】http://mp.163.com/article/FJM5K1UG0511D3QS.html

【5】https://blog.csdn.net/valada/article/details/101570012

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
并肩“测”通关——《魂斗罗:归来》“情怀”战略背后的质量战略
6月,拥有著名游戏IP,击中玩家“情怀”痛点的手游《魂斗罗:归来》启动不删档测试,上线后不久就杀进国内各家应用分发平台畅销榜前三甲,良好势头一直保持至今。
WeTest质量开放平台团队
2018/10/29
6370
从轻测到上线,WeTest与《一起来捉妖》测试方案大公开
原文链接:https://wetest.qq.com/lab/view/455.html
WeTest质量开放平台团队
2019/04/26
8240
从轻测到上线,WeTest与《一起来捉妖》测试方案大公开
腾讯首款战争策略手游「乱世王者」的兼容测试之路
本文主要讲述了《乱世王者》手游在腾讯WeTest的兼容性测试服务支持下,如何实现高质量游戏体验的过程。文章首先介绍了《乱世王者》手游的背景,然后详细描述了WeTest在兼容性测试方面所提供的服务,包括测试流程、机型覆盖、系统版本支持等方面。通过这些服务,WeTest帮助《乱世王者》手游在上线前发现和修复了171个兼容性问题,从而保障游戏在上线后能够稳定运行,为玩家提供良好的游戏体验。
WeTest质量开放平台团队
2017/10/20
2.7K0
腾讯首款战争策略手游「乱世王者」的兼容测试之路
安卓碎片化怎么破?腾讯” 老“测试与您分享手游兼容性测试心得
作为一个移动端开发者,你一定对测试机再熟悉不过,或多或少,你总接触过十几、几十台测试机,但对于在测试机上找BUG的你,或走马观花,或苦苦寻找,是否真的了解你究竟在测些什么?有哪些东西需要测?测多少台手机才够?甚至……花这些钱买测试机和精力来做测试是不是必要?有这些个时间,是不是跟团队里的兄弟撸个串巩固下友谊来的更实在?!
WeTest质量开放平台团队
2018/10/29
2.4K0
从轻测到上线,WeTest与《一起来捉妖》测试方案大公开
从2016年Pokémon GO引发的AR游戏热潮开始,国内就一直在期待新的一款具备代表性的AR游戏的头部作品。 4月11日的腾讯首款AR探索手游《一起来捉妖》不仅为国内市场注入了新的活力,也在上线后迅速登顶免费榜,获得了很好的收效。 合作背景 《一起来捉妖》游戏中利用定位与AR虚实结合,打破次元壁,刮起一阵出门捉妖的热风。在游戏中可以进行实景捉妖,在线组队,挑战擂台,聊天交友等娱乐玩法。游戏依托于陀螺仪结合ARKit技术,在将游戏与真实世界打通的同时,也给整体的游戏质量提出了全新的考验。 游戏内
WeTest质量开放平台团队
2019/05/16
6600
从轻测到上线,WeTest与《一起来捉妖》测试方案大公开
如何规避适配风险?以《乱世王者》为例,探秘手游兼容性测试之路
本文就将阐述腾讯WeTest如何为该游戏进行兼容性测试。
腾讯游戏云
2018/02/05
3.2K0
如何规避适配风险?以《乱世王者》为例,探秘手游兼容性测试之路
一分钟读懂兼容报告:测试过程视频复现,问题定位很轻松
原文链接:https://wetest.qq.com/lab/view/444.html
WeTest质量开放平台团队
2019/03/12
6150
一分钟读懂兼容报告:测试过程视频复现,问题定位很轻松
WeTest全球化服务,为使命召唤手游质量保驾护航
原文链接:https://wetest.qq.com/lab/view/474.html
WeTest质量开放平台团队
2019/10/21
8060
WeTest全球化服务,为使命召唤手游质量保驾护航
谈谈龙之谷手游兼容测试的一百个坑
WeTest质量开放平台团队
2017/05/31
1.7K0
谈谈龙之谷手游兼容测试的一百个坑
“腾讯WeTest助力《龙珠直播》盘点APP质量问题”
原文链接:https://wetest.qq.com/lab/view/408.html
WeTest质量开放平台团队
2018/09/24
6590
“腾讯WeTest助力《龙珠直播》盘点APP质量问题”
浅酌 iOS 11 兼容性
WeTest质量开放平台团队
2017/08/31
1.3K0
浅酌 iOS 11 兼容性
我们用超火的几款手游和应用助您提前了解Andriod P Beta 2的兼容性问题
就在上周,谷歌隆重推出 Android P Beta 2。在此次更新中,添加了 Android P 最终版本 API,最新的系统映像以及更新后的开发者工具,助力开发者们做好准备应对即将在今夏发布的 Android P 正式版。 为全面了解Android P Beta 2的兼容效果究竟如何,腾讯WeTest致力于与谷歌一起共创良好的开发者环境,对Android P进行了一次“兼容性会诊”。 经过双方的技术专家沟通,统一选择了线上版本。针对不同的类型手游,选择了“赛车跑酷”“战术竞技”“角色扮演”“卡牌游戏”“
WeTest质量开放平台团队
2018/07/11
7450
月活8.89亿背后:微信工程师细数兼容测试经验
WeTest质量开放平台团队
2017/05/02
3.2K0
月活8.89亿背后:微信工程师细数兼容测试经验
用上WeTest适配测试,流失用户减少一半!
为什么需要适配测试? 近期,腾讯游戏工作室对近2000名流失玩家进行了一次问卷调查活动。
WeTest质量开放平台团队
2018/10/29
5610
天下大事必作于细,聊聊腾讯兼容测试的升级“打怪”历程
本文主要讲述了腾讯游戏兼容性测试团队如何通过人工测试和自动化测试相结合的方式,提高测试效率和质量,从而提升游戏的用户体验。具体来说,本文介绍了团队的发展历程、工作成果、面临的挑战以及未来的发展方向。此外,本文还分享了团队在提升测试效率和质量方面的具体做法,包括采用自动化测试框架、制定测试标准和规范、提高测试人员的技术水平等。通过这些措施,腾讯游戏兼容性测试团队成功地提高了测试效率和质量,为腾讯游戏的用户体验提供了有力的保障。
WeTest质量开放平台团队
2017/04/26
1.1K0
天下大事必作于细,聊聊腾讯兼容测试的升级“打怪”历程
ipa包兼容性大作战!WeTest iOS深度兼容测试全新升级
原文链接:https://wetest.qq.com/lab/view/447.html
WeTest质量开放平台团队
2019/03/22
1.8K0
ipa包兼容性大作战!WeTest iOS深度兼容测试全新升级
「深度兼容测试」服务 今日重磅发布!
原文链接:http://wetest.qq.com/lab/view/396.html
WeTest质量开放平台团队
2018/07/27
1.9K0
「深度兼容测试」服务 今日重磅发布!
让您的应用完美适配 Android Oreo
自 Android Oreo 面世以来,无数开发者都摩拳擦掌,想让自己的应用在这个全新版本的 Android 系统上大展身手。Google 于 2017 年 12 月 5 日正式发布 Android 8.1 Oreo,不仅引入了对 Android Oreo (Go 版本) 的支持,力求为较低硬件配置和带宽条件的设备带来更理想的用户体验。同时还引入神经网络 API,为应用提供了一个利用硬件加速的机器学习运行时,真正开始让机器学习来到每一人的掌间。 Android Oreo 新特性回顾 Android Oreo
Android 开发者
2018/05/31
9310
WeTest全球化服务,为使命召唤手游质量保驾护航
导读 使命召唤系列作为经典FPS游戏,以良好的表现与出色的射击手感,颠覆了玩家对传统第一人称射击的传统观念。同名手游(CODM)10月份在海外上线,仅一周内下载量就已突破一亿次,更是横扫139个国家及地区的APP Store免费榜单,无疑是非常成功的一款手游。 为打造高还原、高品质的产品,CODM研发团队和腾讯WeTest测试团队达成合作,针对 CODM特点及全球化拓展的战略目标,结合其手机端的操作特点,做了一系列适配与优化工作。 一站式服务:助力《使命召唤手游》品质保障 手游上线前需要
WeTest质量开放平台团队
2019/10/21
4960
WeTest全球化服务,为使命召唤手游质量保驾护航
WeTest腾讯质量开放平台内测开启
WeTest腾讯质量开放平台(wetest.qq.com)于2015年1月22日开启内测。
WeTest质量开放平台团队
2018/10/29
1.7K0
推荐阅读
相关推荐
并肩“测”通关——《魂斗罗:归来》“情怀”战略背后的质量战略
更多 >
目录
  • 1,简介
  • 2,变量声明与使用
    • 2.1,默认不可变
    • 2.2,使用之前必须初始化
    • 2.3,默认move语义
  •  3,所有权
    • 3.1,use of moved value
    • 3.2,借用默认不可变
    • 3.3,不能同时有两个可变借用
    • 3.4,不能同时有可变借用与不可变借用
    • 3.5,严谨性不能覆盖的一面
    • 3.6,借用的有效性
  • 4,内存安全
    • 4.1,非法内存使用
    • 4.2,空指针
  • 5,其他
  • 6,参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档