Rust语言(一)

Rust 简介

使 Rust 显得独一无二的主要概念是“所有权”。考虑这个小例子:
fn main() {
    let mut x = vec!["Hello", "world"];
}
这个程序创建了一个叫做x的变量绑定。这个绑定的值是一个Vec<T>,一个 vector,我们通过一个定义在标准库中的宏来创建它。这个宏叫做vec,并且我们通过一个!调用宏。这遵循了 Rust 的一般原则:让一切明了。宏可以做比函数调用复杂的多的多的工作,并且它们在视觉上也是有区别的。!也方便了解析,更容易编写工具,这也是很重要的。
我们使用了mut来使x可变:在 Rust 中绑定是默认是不可变的。在下面的例子中这个 vector 是可变的。
另外值得注意的是这里我们并不需要一个类型注释:因为 Rust 是静态类型的,我们并不需要显式的标明类型。Rust 拥有类型推断来平衡静态类型的能力和类型注释的冗余。
Rust 与堆分配相比倾向于栈分配:x被直接储存在栈上。然而,Vec<T>类型在堆上为 vector 的元素分配了空间。如果你并不熟悉这里的区别,目前你可以忽略它,或者看看“栈和堆”。作为一个系统编程语言,Rust 给予你控制内存分配的能力,不过当我们上手后,这并不是什么大问题。
之前,我们提到“所有权”是 Rust 中的一个关键概念。在 Rust 用语中,x被认为“拥有”这个 vector。这意味着当x离开作用域,vector 的内存将被销毁。这由 Rust 编译器决定,而不是通过类似垃圾回收器这样的机制。换句话说,在 Rust 中,你并不需要自己调用像malloc和free这样的函数:编译器静态决定何时你需要分配和销毁内存,并自动调用这些函数。人非圣贤孰能无过,不过编译器永远也不会忘记。
让我们为例子再加一行:
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()之前离开作用域,所以我们不会碰到问题。
所有权的概念并不仅仅善于防止悬垂指针,也解决了一整个系列的相关问题,比如迭代器无效,并发和其它问题。

 

posted @ 2017-05-08 08:20  llluiop  阅读(318)  评论(0编辑  收藏  举报