返回首页 Rust 语言中文版

语法和语义

介绍

Rust 编程语言

欢迎学习本教程!本教程将教你如何使用 Rust 编程语言。Rust 是一门强调安全、性能和并发性的系统编程语言。它为了达到这几个目的,甚至没有一个垃圾收集器。这也使 Rust 能够应用到其他语言做不到的地方:嵌入到其他语言,有指定空间和时间需求的程序,写底层代码(如设备驱动程序和操作系统)。针对当前的其他编程语言,Rust 做到了没有运行时(Runtime),没有数据竞争。 Rust 也致力于实现“零成本抽象”,尽管这些抽象给人的感觉像一个高级的语言。即使是这样,Rust 仍然可以做到像一个低级的语言那样的精确控制。

“Rust 编程语言”分为七个部分。本文的简介是第一个。在这之后:

  • 新手入门 - 设置您的电脑来进行 Rust 开发。
  • 学习 Rust - 通过小型项目学习 Rust 编程。
  • 高效的 Rust - 学习编写优秀 Rust 代码的一些高级概念。
  • 语法和语义 - Rust 的每一部分,分解成小块来讲解。
  • 每日 Rust - 尚未构建稳定的一些高端特性。
  • 术语 - 本教程的相关参考科目。
  • 学术研究 - 影响 Rust 的一些著作。

阅读本文之后,你会想了解“学习 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<T>,是我们通过在标准库中定义的宏创建的一个'向量'。这个宏被称为 Vec,我们利用!调用宏用。这遵循 Rust 的一般原则:做事情要明确。宏可以有效的做一些比函数调用更复杂的东西,所以他们外观上来看是不一样的。这个符号!也有助于解析,使代码更容易编写,这也很重要。

我们使用 mut 使 x 可变:默认情况下,Rust 中的绑定是不可变的。我们将在后面的例子中改变此向量。

另外值得一提的是,在这里我们不需要标注类型:Rust 是静态类型,我们并不需要再明确标注类型。Rust 有类型推断,用以平衡强大的静态类型和冗长标注类型。

Rust 更倾向于堆栈分配而不是堆分配:x 被直接放置在堆栈中。然而,Vec<T> 类型是在堆上分配的向量元素的空间。如果你不熟悉它们之间的区别,那么你现在可以先忽略它,或者你可以查看章节“堆栈和堆”,来了解详细了解。作为一个系统编程语言,Rust 赋予了你如何分配内存空间的能力,但是在我们开始的阶段,这是并不是一个大问题。

此前,我们提到的“所有权”是铁锈的关键新概念。生锈的说法,X 被说成“自己”的载体。这意味着,当 x 超出范围,载体的存储器将被解除分配。这是由防锈编译确定性完成,而不是通过一个机制,诸如垃圾收集器。换句话说,在防锈,你不叫喜欢的 malloc 函数和释放自己:编译静态判断,当你需要分配或释放内存,并插入这些调用本身。犯错是做人,但编译器永远不会忘记。

前面我们所提到的,“所有权”是 Rust 中的一个非常重要的新概念。按照 Rust 的说法,x 被称为向量“所有”。这意味着当 x 超出范围,向量的内存将被销毁。这样做是由 Rust 的编译器所决定的,而不是通过一种机制(如垃圾收集器)所决定的。换句话说,在 Rust 中,你不需要自己调用函数,如 mallocfree yourself: 当你需要分配或释放的内存时,编译器会自行静态的决定并插入这些调用函数。犯错是人之常情,但编译器永远不会忘记插入这些调用的函数。

让我们在我们上面的例子中添加另外的一行代码:

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

        let y = &x[0];
    }

我们在此介绍了另外的一种绑定 ,y。在这种情况下,y 是向量第一个元素的一个“引用”。Rust 的引用与其他语言中的指针类似,不同的是有额外的编译时安全检查。特别指出的是,引用与所有权系统通过“借用”来相互作用,而不是通过拥有它。不同的是,当引用超出范围时,它不会释放底层的内存。如果是那样,我们将会释放两次内存,这是显然是不正确的!

让我们来添加第三行代码。表面上来是没错的,但是它会导致一个编译错误:

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

        let y = &x[0];

        x.push("foo");
    }

push 是将一个元素附加到向量组末端的方法。当我们试图编译这个程序时,我们将得到一个错误:

    error: cannot borrow `x` as mutable because it is also borrowed as immutable
        x.push("foo");
        ^
    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 方法。这是因为我们已经有了一个矢量元素的引用 ,y。当另外的一个引用存在时,改变某些变量是危险的行为,因为我们可能导致引用的无效。在这个特定的例子中,当创建向量时,我们可能只分配了三个元素的空间。添加第四个元素意味着所有这些元素将会被分配一块新的块内存,同时复制旧值到新内存,更新内部指针到新内存。这些工作都没有什么问题。问题是, y 不会更新,所以我们会有一个“悬空指针”。那就不对了。在这种情况下,任何的使用引用 y,将会导致错误,所以编译器已经帮我们捕捉到了这个错误。

那么,我们如何解决这个问题呢?有两种我们可以采用的方法。第一种方法,我们利用拷贝而不是一个引用:

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

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

        x.push("foo");
    }

在默认情况下,Rust 有移动语义,所有如果我们想要复制一些数据,我们可以调用 clone() 方法。在这个例子中 ,y 不再是存储在 x 中向量的一个引用,而是它的第一个元素“ Hello ”的一个副本。现在没有引用,我们的 push() 方法可以很好地运行。

如果我们真的想用一个引用,我们需要另外一种选择:确保在我们尝试做改变之前,我们的引用跳出其作用域。写法看起来像这样:

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

        {
            let y = &x[0];
        }

        x.push("foo");
    }

我们用一组额外的大括号创建了一个内部作用域。在我们调用 push() 之前,y 已经跳出其作用域。所以这样是可行的。

所有权的概念不仅有利于防止悬空指针,而且有利于解决与其相关的所有问题,如迭代器失效,并发性等等。

上一篇: 关于 下一篇: 新手入门