Skip to content

Latest commit

 

History

History
78 lines (54 loc) · 6.39 KB

r4cpp - Rc and raw pointers.md

File metadata and controls

78 lines (54 loc) · 6.39 KB

原文: https://github.com/nrc/r4cppp/blob/master/rc%20raw.md 翻译者: Scott Huang 翻译日期: Agust 29, 2015 于厦门

Reference counted and raw pointers | 引用计数和原始指针

TODO: 拟加讨论自定义指针和Defer特质(也许以后,不在这里)

到目前为止我们已经涵盖了所有权指针(或者 unique 独特指针)和借贷的指针。所有权指针是非常 类似C++中的new std::unique_ptr,而借贷指针类似你在C++中通常使用的“默认”的指针或引用。Rust还有一些罕见的指针在库里或者内建在语言里。这些大多是类似于C++中你已经习惯的的各种智能指针。

这篇文章花了一段时间来写,但我还是不喜欢它。有许多零碎资料,有些是我导致的,有些是Rust本身导致的。我希望有些会在以后的文章中得到改善,而有些则随着语言的发展而会变得更好。如果你在学习Rust,你可能甚至想跳过这个内容,现在,希望你不需要它。它在这里只是为了续上前几篇关于指针类型的文章。

你可能会感觉Rust有很多指针类型,但它是和C++非常相似的,一旦你想到了C++库里各种各样的智能指针。然而,在Rust里,你更有可能在你刚开始学习语言时就见到这些指针。因为Rust指针有编译器支持,你在使用它们时会有更少的机会出现错误。

我不打算像讨论独特指针或借贷指针那样涉及到许多细节,因为,坦率地说,他们并不重要。我可能稍后会再回来讨论更多细节。

Rc 引用计数

引用计数指针是Rust标准库的一部分。他们在std::rc模块(我们将很快涉及到模块。模块是在实例使用use的原因)。一个指向类型对象T的引用计数的指针具有类型Rc<T>。创建引用计数指针使用静态方法(现在你可以认为类似于C++,但我们后面会看到有点不同)-Rc::new(...)需要一个值来创建指向的指针。这种构造方法遵循了Rust的一般移动/复制的语义 (如我们讨论的所有权)-在任何情况下,在调用Rc::new后,您将只能通过指针访问该值。

正如其他类型的指针,.算子做了所有你需要的解引用动作。你可以使用*手动解引用。

传递引用计数的指针需要使用clone克隆)方法。这有点糟糕,希望我们会解决,但(可悲的)是不确定。你可以 用(借用的)指针指向值,所以希望你不需要太经常克隆。Rust的类型系统保证了引用计数的变量在任何引用过期之前不会被删除。采用一个引用的额外好处是它不需要递增或递减ref计数,这样的性能会更好(尽管,区别也许很微小,因为Rc的对象局限于一个单独的线程,所以引用计数操作不必是原子的)。正如在C++,你也可以用对Rc指针取引用。

一个Rc的例子:

use std::rc::Rc;

fn bar(x: Rc<i32>) { }
fn baz(x: &i32) { }

fn foo() {
    let x = Rc::new(45);
    bar(x.clone());   // 增加计数
    baz(&*x);         // 不增加计数
    println!("{}", 100 - *x);
}  // 当到达范围时,所有的计数指针(Rc pointers)会消失,所以ref-count == 0, 并且以前分配的内存会被删除。

引用计数的指针总是不变的。如果你想要一个可变的引用计数对象,你需要使用RefCell(或Cell)来包裹一个Rc

Cell and RefCell | 细胞和引用细胞

Cell和RefCell一种允许你“欺骗”的可变性规律的结构(structs)。如果你第一次涉及Rust数据结构及 他们怎样处理可变性的话,将很难向你解释清楚它,所以我要稍后回到这些略棘手的对象。现在,你应该知道,如果你想要一个可变的,引用计数对象,你需要一个Cell(细胞)或RefCell包裹一个Rc。作为第一 尝试,你可能需要为了原始数据而需要Cell,为了对象而需要RefCell,且移动语义。因此,对一个可变的,引用计数的int你需要使用Rc<Cell<int>>.

*T - raw pointers | 原始指针

最后,Rust有两种原始指针(又名不安全的指针):*const T是一个不可变的原始指针,*mut T是一个可变的原始指针。他们是使用&&mut来创建(你也许需要指定类型来获取*T而不是一个&T因为&操作符可以创建一个借贷指针或者一个原始指针)。原始指针是Rust里面唯一的一种可以拥有null值的指针。原始指针没法自动解引用(所以,为了调用一个方法,你不得不写成(*x).foot(),并且没有自动引用。最重要的限制是他们不能在一个unsafe块的外面被解引用(且因此不能被使用)。在普通的Rust代码中,你只可以传递他们。

那么,什么是不安全的代码?Rus有很强的安全保障,而且他们很少阻止你做你需要做的事。因为Rust是一种系统语言,它必须在必要的时候能够做任何事情,有时这意味着所做事情的编译器无法验证是安全的。要做到这一点,Rust有不安全的块的概念,用“unsafe”的关键字做标志。在不安全的代码中,你可以做不安全的事-用原始指针,数组索引没有边界检查,通过FFI调用另外一种语言写的代码,或转换变量。很显然,相对于普通的代码,你需要很小心的写不安全的代码。事实上,你应该很少写不安全码。它主要是用在非常小的库中,而不是在客户代码中。在不安全的代码中,你必须做向你通常在C++中所做的那样来确保安全。此外,您必须手动确保您保持编译器通常会强制执行的不变量。不安全的块允许你手动确保Rust的不变量,它不允许你打破那些不变量。如果你做了,你会在安全和不安全的代码中引入错误。

使用原始指针的示例:

fn foo() {
    let mut x = 5;
    let x_p: *mut i32 = &mut x;
    println!("x+5={}", add_5(x_p));
}

fn add_5(p: *mut i32) -> i32 {
    unsafe {
        if !p.is_null() { // 注意这个 * -指针没有自动解引用,所以这是一个实现在*i32的方法,而不是i32
                          
            *p + 5
        } else {
            -1            // 不是一个推荐的错误处理策略
        }
    }
}

我们关于Rust指针的讨论在这里结束。下次我们要休息一下,不谈指针而谈Rust的数据结构。但是,我们在后面的文章继续谈借贷指针。