文字

Rust编程语言

欢迎阅读!这本书将教会你使用Rust编程语言。Rust是一个注重安全与速度的现代系统编程语言,通过在没有垃圾回收的情况下保证内存安全来实现它的目标,这使它成为一个在很多其它语言不适合的用例中大展身手的语言:嵌入到其它语言中,在特定的时间和空间要求下编程,和编写底层代码,例如设备驱动和操作系统。它通过一系列的不产生运行时开销的编译时安全检查来提升目前语言所关注的这个领域,同时消除一切数据竞争。Rust同时也意在实现“零开销抽象”,即便在这些抽象看起来比较像一个高级语言的特性。即便如此,Rust也允许你像一个底层语言那样进行精确的控制。

《Rust编程语言》被分为7个部分。这个介绍是第一部分。之后是:

在阅读了介绍这部分之后,你可以根据喜好深入到“学习Rust”或“语法和语义”部分:如果你想通过项目深入了解,可以先选择“学习Rust”;如果你想从头开始,并且学习一个完整的内容再学习另一个,你可以从“语法和语义”开始。丰富的交叉连接将这些部分联系到一起。

贡献

生成这本书的源文件可以在GitHub上找到:github.com/rust-lang/rust/tree/master/src/doc/trpl

Rust简介

Rust是你会感兴趣的语言吗?让我们检查一些小的代码例子来展示它的部分威力。

使Rust显得独一无二的主要概念是“所有权”。考虑这个小例子:

fn main() {
    let mut x = vec!["Hello", "world"];
}

这个程序创建了一个叫做x变量绑定。这个绑定的值是一个Vec,一个vector,我们通过一个定义在标准库中的来创建它。这个宏叫做vec,并且我们通过一个!调用宏。这遵循了Rust的一般原则:让一切明了。宏可以做比函数调用复杂的多的多的工作,并且它们在视觉上也是有区别的。!也方便了解析,更容易编写工具,这也是很重要的。

我们使用了mut来使x可变:再Rust中绑定是默认是不可变的。在下面的例子中这个vector是可变的。

另外值得注意的是这里我们并不需要一个类型注释:因为Rust是静态类型的,我们并不需要显式的标明类型。Rust拥有类型推断来平衡静态类型的能力和类型注释的冗余。

Rust与堆分配相比倾向于栈分配:x被直接储存在栈上。然而,Vec类型在堆上为vector的元素分配了空间。如果你并不熟悉这里的区别,目前你可以忽略它,或者看看“栈与堆”。作为一个系统编程语言,Rust给予你控制内存分配的能力,不过当我们上手后,这并不是什么大问题。

之前,我们提到“所有权”是Rust中的一个关键概念。在Rust用语中,x被认为“拥有”这个vector。这意味着当x离开作用域,vector的内存将被销毁。这由Rust编译器决定,而不是通过类似垃圾回收器这样的机制。换句话说,在Rust中,你并不需要自己调用像mallocfree这样的函数:编译器静态决定何时你需要分配和销毁内存,并自动调用这些函数。人非圣贤孰能无过,不过编译器永远也不会忘记。

让我们为例子再加一行:

fn main() {
    let mut x = vec!["Hello", "world"];

    let y = &x[0];
}

我们引入了另一个绑定,y。在这个例子中,y是对vector第一个元素的“引用”。Rust的引用类似于其它语言中的指针,不过带有额外的编译时安全检查。引用用“借用”它指向的内容,而不是拥有它,来与所有权系统交互。这里的区别是,当一个引用离开作用域,它不会释放之下的内存。如果它这么做了,我们会释放两次,这是很糟的!。

让我们增加第三行。这看起来并不会引起错误,不过实际上会造成一个编译错误:

fn main() {
    let mut x = vec!["Hello", "world"];

    let y = &x[0];

    x.push("foo");
}

push是vector的一个方法,它在vector的末尾附加另一个元素。当我们尝试编译这个程序时,我们得到一个错误:

error: cannot borrow `x` as mutable because it is also borrowed as immutable
    x.push(4);
    ^
note: previous borrow of `x` occurs here; the immutable borrow prevents
subsequent moves or mutable borrows of `x` until the borrow ends
    let y = &x[0];
             ^
note: previous borrow ends here
fn main() {

}
^

噢!Rust编译器有时给出灰常详细的错误,而这就是其中之一。正如错误所解释的,当我们让绑定可变,我们仍不能调用push。这是因为我们已经有了一个vector元素的引用,y。当有其它引用存在时改变值是危险的,因为我们可能使这个引用无效。在这个特定的例子中,当我们创建了vector,我们可能只分配了3个元素的空间。增加一个元素意味着将分配一个新的能放下所有4个元素的空间,拷贝旧的值,并更新内部的指针指向这个内存。所有这些都木有问题。问题是y并没有被更新,很糟糕地,y成了一个“悬垂指针”(dangling pointer)。因此,在这个例子中任何对y的使用都会引起错误,而编译器会为我们捕获了这个错误。

那么我们应该如何解决这个问题呢?这里我们可以采取两个方法。第一个方法是使用拷贝而不使用引用:

fn main() {
    let mut x = vec!["Hello", "world"];

    let y = x[0].clone();

    x.push("foo");
}

Rust默认拥有移动语义,所以如果我们想要拷贝一些数据,我们调用clone()方法。在这个例子中,y不再是一个储存在x中vector的一个引用,而是它第一个元素的拷贝,"hello"。现在我们并不拥有一个引用,所以push()就能正常工作。

如果我们真心需要一个引用,我们需要另一种方法:确保在我们尝试修改之前,让引用离开作用域。如下:

fn main() {
    let mut x = vec!["Hello", "world"];

    {
        let y = &x[0];
    }

    x.push("foo");
}

我们用一对大括号创建了一个内部作用域,y会在我们调用push()之前离开作用域,所以我们不会碰到问题。

所有权的概念并不仅仅善于防止悬垂指针,也解决了一整个系列的相关问题,比如迭代器无效,并发和其它问题。

上一篇: 下一篇:
  页面正在加载中