周末学了点 Rust简介工具链宏(macros)返回值和错误处理Ownership 和生命周期闭包小结参考文档

简介

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 真的挺复杂很多,后面都不知道还有多少坑。

参考文档

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏惨绿少年

Shell编程基础篇-上

1.1 前言 1.1.1 为什么学Shell Shell脚本语言是实现Linux/UNIX系统管理及自动化运维所必备的重要工具, Linux/UNIX系统的底层...

2000
来自专栏Python自动化测试

selenium框架浅谈

我们知道,selenium是一个很优秀的web框架,提供了很丰富的API,使用它结合进行做web的自动化测试真的很完美,但是在实际的情况中,理想与现实...

823
来自专栏机器之心

资源 | 简单快捷的数据处理,数据科学需要注意的命令行

1645
来自专栏青玉伏案

关于Simple_html_dom的小应用

  今天一同学给我推荐了本书,说是刚出不久,内容还不错,是心灵鸡汤类的书,于是按捺不住就像在网上下一本,可是木有资源肿么办。只有在线看的,作为一个准码农,所以甭...

2177
来自专栏逍遥剑客的游戏开发

Ogitor代码分析

1282
来自专栏Java3y

【Java】几道让你拿offer的面试题

之前在刷博客的时候,发现一些写得比较好的博客都会默默收藏起来。最近在查阅补漏,有的知识点比较重要的,但是在之前的博客中还没有写到,于是趁着闲整理一下。

3050
来自专栏肖洒的博客

Python100Days

这可能是我目前发现最好最好的Python教程了,故整理至我的博客。 原项目GitHub地址https://github.com/jackfrued/Python...

1.4K3
来自专栏同步博客

降低Redis内存占用

  Redis为列表、集合、散列、有序集合提供了一组配置选项,这些选项可以让redis以更节约的方式存储较短的结构。

1131
来自专栏Java3y

多线程基础必要知识点!看了学习多线程事半功倍

2098
来自专栏喔家ArchiSelf

全栈必备JavaScript基础

1995年,诞生了JavaScript语言,那一年,我刚刚从大学毕业。在今年RedMonk 推出的2017 年第一季度编程语言排行榜中,JavaScript 排...

1244

扫码关注云+社区