周末学了点 Rust

简介

Rust 是最近几年开始兴起的编程语言,虽然目前还没看到要像 Go 一样”大火“的趋势。但是,官网的一些 featuring 看着就很让人心动(虽然还不知道现实如何~)。

Rust 的官网介绍:

Rust is a systems programming language that runs blazingly fast, prevents segfaults, and guarantees thread safety.

Featuring

  • zero-cost abstractions
  • move semantics
  • guaranteed memory safety
  • threads without data races
  • trait-based generics
  • pattern matching
  • type inference
  • minimal runtime
  • efficient C bindings

零抽象开销、高效的 C 语言绑定、内存安全、线程安全…看起来就是一个现代 C++ 的加强版。

工具链

目前, Rust 的开发速度很快,保持着每 6 周更新一次的节奏。Rust 发布的工具链包括了 stablebetanightly 三种不同版本。 nightly 是最激进的版本,包含了大量(可能不稳定)的新/高级特性。stable 版本目前可能还不支持一些高级特性。beta 介于两者之间。

rustup

因为 Rust 的更新速度很快,支持的版本很多,有时新版本是不会完美兼容旧版本的,同时还支持多平台交叉编译。所以就有了 rustup 这个 Rust 工具链的管理工具。

安装

安装 Rust 就是从安装 rustup 开始,很方便,可以参考这个页面

介绍

关于 rustup 的介绍和使用,具体可以参考这个页面

# 更新工具链
rustup update

# 更新 rustup 本省
rustup self update

# 安装最新的 nightly 版本
rustup install nightly

# 指定版本
rustup install nightly-2018-07-18

# 指定默认使用的版本
rustup default nightly

cargo

使用 rustup 安装 Rust 时,主要安装了:

  • rustc - 编译器。
  • rust-std - 标准库。
  • cargo - 包管理工具。
  • rust-doc - 说明文档。

其中,最重要是 cargo 这个包管理工具,rustc 编译器的使用,大部分也是通过 cargo 来调用的。
cargo 是一个 Rust 下一个十分强大的工具,详细信息可以参考 The Cargo Book

宏(macros)

第一个 Rust 程序,著名的 Hello World 系列(参考自这个页面)。

fn main() {
    println!("Hello, world!");
}

打印一句Hello, world! ,然后换行,多简单啊!

但是看到 println! 那个感叹号时,我的强迫症要爆发了 —— 这个“函数名”为什么要多一个感叹号!!!

往下看,发现 xyz! 这种东东在 Rust 中叫做 宏(macros)

C++ 里面也有宏,从 C 那里继承过来的。对 C++ 来说,其实宏的能力很弱小 —— 虽然可能通过各种奇技淫巧、很费劲地写出功能强大的代码,但是相对地也会大大降低代码的可读性和可维护性。(C++ 中复杂的宏,估计过几个月,原作者都不敢随便改动了…直接的字符串替换,在不确定用户使用场景的时候,非常容易出问题。)

Rust 中的宏功能强大、严谨很多。

Fundamentally, macros are a way of writing code that writes other code, which is known as metaprogramming.

我只是看了一下文档,没真正写过 Rust 的宏,有兴趣的可以看看这个官方文档——Appendix D: Macros

返回值和错误处理

在 Rust 的世界里,错误分成两种 recoverable 和 unrecoverable 。

  • recoverable error,比如“打开一个不存在的文件”。
  • unrecoverable error,比如“访问数组越界”。

unrecoverable

遇到 unrecoverable error 时,程序会调用 panic!宏,然后进程就挂了。看了一下文档,很简单,具体可以参考这个页面

recoverable

写 Rust 代码时,遇到比较多的是 recoverable error。下面简单说一下,主要内容来自这个页面

recoverable error 通过函数的返回值来表示。这一点,Rust 和 Go 一样,都抛弃了 exception 风格的错误处理方式。

不同的是,Go 通过多个函数返回值来返回数据+错误信息,Rust 则搞了一个一开始看起来比较奇怪的返回值 —— Result<T, E>

Result<T, E> 的定义如下:

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

使用示例:

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let f = File::open("hello.txt");

    let f = match f {
        Ok(file) => file,
        Err(ref error) if error.kind() == ErrorKind::NotFound => {
            match File::create("hello.txt") {
                Ok(fc) => fc,
                Err(e) => {
                    panic!(
                        "Tried to create file but there was a problem: {:?}",
                        e
                    )
                },
            }
        },
        Err(error) => {
            panic!(
                "There was a problem opening the file: {:?}",
                error
            )
        },
    };
}

作为一个 Rust 的初学者,我觉得 Rust 这样的返回值,错误处理的代码看起来一点都不清晰,有点繁琐…
可能是 c++ 写多了,个人还是比较习惯 Go 那种多个返回值的错误处理方式,虽然 C++ 不支持多个返回值。

为了简化 Rust 的错误处理代码,Restlt<T, E> 实现了一些错误处理的封装:unwrapexpect

unwrap

If the Result value is the Ok variant, unwrap will return the value inside the Ok. If the Result is the Err variant, unwrap will call the panic! macro for us.

使用示例:

use std::fs::File;

fn main() {
    let f = File::open("hello.txt").unwrap();
}

expect

expect, which is similar to unwrap, lets us also choose the panic! error message. Using expect instead of unwrap and providing good error messages can convey your intent and make tracking down the source of a panic easier.

使用示例:

use std::fs::File;

fn main() {
    let f = File::open("hello.txt").expect("Failed to open hello.txt");
}

the ? Operator

有时候,我们写一个函数,只想把更底层的错误直接传递给上一层。

代码示例:

use std::io;
use std::io::Read;
use std::fs::File;

fn read_from_file() -> Result<String, io::Error> {
    let f = File::open("hello.txt");

    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut s = String::new();

    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}

上面的代码封装了一个直接读取文件数据的函数 read_from_file 。当打开文件或读取文件数据出错时,我们希望把错误传递给调用方,而不是直接 panic!,所以不能使用 unwrapexpect 。像最原始那样写错误处理代码可以解决这个问题,但是代码也显得很繁琐。

所以,Rust 有提供了一个语法糖—— the ? Operator。上面的例子可以改写成:

use std::io;
use std::io::Read;
use std::fs::File;

fn read_from_file() -> Result<String, io::Error> {
    let mut f = File::open("hello.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

进一步简化上面的代码:

use std::io;
use std::io::Read;
use std::fs::File;

fn read_username_from_file() -> Result<String, io::Error> {
    let mut s = String::new();

    File::open("hello.txt")?.read_to_string(&mut s)?;

    Ok(s)
}

Ownership 和生命周期

常见的内存管理方式有两种:

  • 一种是使用 Garbage Collection,如 Java、Go。
  • 另一种是由开发者主动分配和释放内存,如 C++。
  • 而 Rust 采用了第三种方式,通过 Ownership 这个特性,可以在编译器对内存的管理进行检查,实现了不需要垃圾回收的内存安全保证(应该主要是保证不发生内存泄漏)。

Ownership 的规则很简单:

  1. Each value in Rust has a variable that’s called its owner.
  2. There can only be one owner at a time.
  3. When the owner goes out of scope, the value will be dropped.

简单地说,就是一块内存,有且只有一个 owner,当这个 owner 离开它的作用域时,内存就会被回收。由此,Rust 引出了 Move 语义 和 Copy 语义。熟悉 C++ 的人对这个两个概念应该比较熟悉。

默认情况下,一些比较“复杂”的对象的赋值都是 Move 语义。某些情况下不想/不能使用 Move 语义,Rust 就引入了 references —— 本质上是指针,而不是 C++ 里的引用。并且,Rust 限制一个对象最多只有一个 mutable reference 或多个 immutable references。

引入指针,就遇到 dangling references(pointers) 的问题。如何在编译期间检测出 dangling references ?

为了防止出现 dangling references,必须保证 references 指向的对象是有效的,即确保 references 的使用是在对象的生命周期(lifetimes)之内。Rust 引入了一种叫做 Lifetime Annotation Syntax 让开发者说明 references 的生命周期,然后在编译期拒绝掉所有生命周期不合法的代码。个人感觉文档说得很有道理,但是又不太好理解,具体可以再看看文档,再研究下其原理。

闭包

闭包其实很简单,概念大部分人应该都懂,只是每种语言都有自己的闭包语法。所以一开始看到 Rust 的闭包代码时,也是摸不清头脑,不知道是在写什么。

Rust 的闭包语法的基本形式是:

|agr1, agr2| { do-something }

当然,这里面又会涉及参数捕获、生命周期等问题。具体看文档吧 —— 介绍闭包的文档

小结

是 Rust 里一个非常重要的特性,代码里的很多地方都可以见到。与 C++ 里的宏不同,Rust 的宏是完全重新设计、被彻底加强过得。

错误处理的代码无处不在,而 Rust 的错误代码写起来又有点“奇葩”,一点都不像在处理错误。

Ownership 和生命周期应该是 Rust 里最普遍、最重要又最难掌握的特性之一

闭包这个很简单,认识一下语法就行。

写这篇文章,主要是记录一下第一次阅读 Rust 的代码时遇到的一些问题,为 Rust 的代码阅读清扫一下障碍,Rust 真的挺复杂很多,后面都不知道还有多少坑。

参考文档

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容

  • 通用编程概念 变量与可变性 变量默认不可变,如需要改变,可在变量名前加 mut 使其可变。例如:let mut a...
    soojade阅读 12,422评论 2 30
  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 13,517评论 0 38
  • "长了个狗鼻子。" 这是我母亲给过我的为数不多的夸奖之一。这倒不是因为我像狗一样听话和忠诚,更不是说我长了一个黑黢...
    鱼我则何如阅读 315评论 0 2
  • 对于 AR,可能有的人是从《Pok mon Go》的身上了解到相关概念,当然也因为这款游戏,企图在 AR 领域捞一...
    幻眼科技阅读 226评论 0 0
  • 标题中最难理解的估计是“续班”这个概念,续班就是教辅行业里的一个说法,说的就是让之前在本机构上课补课的学生接着在本...
    夜猫小七阅读 877评论 0 2