首页 文章

Box,ref,&和*之间的理解和关系

提问于
浏览
42

我'm a bit confused about how pointers work in Rust. There' s refBox&* ,我不确定它们是如何协同工作的 .

这是我目前的理解方式:

  • Box isn 't really a pointer - it'是一种在堆上分配数据的方法,并在函数参数中传递未定义的类型(特别是traits) .

  • ref 用于模式匹配,借用你匹配的东西,而不是拿它 . 例如,

let thing: Option<i32> = Some(4);
match thing {
    None => println!("none!"),
    Some(ref x) => println!("{}", x), // x is a borrowed thing
}
println!("{}", x + 1); // wouldn't work without the ref since the block would have taken ownership of the data
  • & 用于进行借用(借用指针) . 如果我有一个函数 fn foo(&self) 那么我就单独使用'm taking a reference to myself that will expire after the function terminates, leaving the caller'数据 . 我也可以通过 bar(&mydata) 传递我想要保留所有权的数据 .

  • * 用于生成原始指针:例如, let y: i32 = 4; let x = &y as *const i32 . 我理解C / C中的指针但我是类型系统,以及它们如何安全使用 . 我也不确定这种指针的用例是什么 . 此外, * 符号可用于取消引用事物(什么东西,为什么?) .

有人可以向我解释第四种指针,并验证我对其他类型的理解是否正确?我也很感激任何人指出我没有提到过的任何常见用例 .

3 回答

  • 8

    首先,你列出的所有项目都是不同的东西,即使它们与指针有关 . Box 是库定义的智能指针类型; ref 是模式匹配的语法; & 是一个引用运算符,在引用类型中加倍作为sigil; * 是一个解引用运算符,在原始指针类型中加倍作为sigil . 有关更多说明,请参见下文

    Rust中有四种基本指针类型,可以分为两组 - 引用和原始指针:

    &T        - immutable (shared) reference
    &mut T    - mutable (exclusive) reference
    
    *const T  - immutable raw pointer
    *mut T    - mutable raw pointer
    

    最后两个之间的差异很小,因为任何一个都可以在没有任何限制的情况下被转换为另一个,所以 const / mut 区别主要用作lint . 可以自由地为任何东西创建原始指针,例如,它们也可以通过整数从空气中创建 .

    当然,对于引用来说并非如此 - 引用类型及其交互定义了Rust的一个关键特性:借用 . 参考文献对如何以及何时创建,如何使用以及它们如何相互交互有很多限制 . 作为回报,它们可以在没有 unsafe 块的情况下使用 . 然而,借用的确切内容及其运作方式超出了这个答案的范围 .

    可以使用 & 运算符创建引用和原始指针:

    let x: u32 = 12;
    
    let ref1: &u32 = &x;
    let raw1: *const u32 = &x;
    
    let ref2: &mut u32 = &mut x;
    let raw2: *mut u32 = &mut x;
    

    可以使用 * 运算符取消引用引用和原始指针,但对于原始指针,它需要 unsafe 块:

    *ref1; *ref2;
    
    unsafe { *raw1; *raw2; }
    

    取消引用运算符通常被省略,因为另一个运算符"dot"运算符(即 . )会自动引用或取消引用其左参数 . 所以,例如,如果我们有这些定义:

    struct X { n: u32 };
    
    impl X {
        fn method(&self) -> u32 { self.n }
    }
    

    然后,尽管 method() 通过引用获取 selfself.n 会自动取消引用它,因此您不必键入 (*self).n . 调用 method() 时会发生类似的事情:

    let x = X { n: 12 };
    let n = x.method();
    

    这里,编译器自动引用 x.method() 中的 x ,因此您不必编写 (&x).method() .

    接下来的最后一段代码也演示了特殊的 &self 语法 . 它仅表示 self: &Self ,或者更具体地说,在此示例中为 self: &X . &mut self*const self*mut self 也有效 .

    因此,引用是Rust中的主要指针类型,应该几乎总是使用 . 原始指针(没有引用限制)应该用于实现高级抽象(集合,智能指针等)和FFI(与C库交互)的低级代码中 .

    Rust也有dynamically-sized (or unsized) types . 这些类型没有明确的静态已知大小,因此只能通过指针/引用使用 . 但是,只有指针是不够的 - 需要额外的信息,例如,切片的长度或指向特征对象的虚拟方法表的指针 . 这些信息在指向未分类的类型的指针中是"embedded",使这些指针成为"fat" .

    胖指针基本上是一个结构,它包含指向数据的实际指针和一些附加信息(切片的长度,指向特征对象的vtable的指针) . 这里重要的是Rust会为用户绝对透明地处理指针内容的这些细节 - 如果你通过了 &[u32]*mut SomeTrait 值左右,相应的内部信息将自动传递 .

    Box<T> 是Rust标准库中的智能指针之一 . 它提供了一种在堆上分配足够内存以存储相应类型的值的方法,然后它用作句柄,指向该内存的指针 . Box<T> 拥有它指向的数据;当它被删除时,堆上的相应内存被释放 .

    考虑框的一种非常有用的方法是将它们视为常规值,但具有固定大小 . 也就是说, Box<T> 等同于 T ,除了它总是占用与机器指针大小相对应的字节数 . 我们说(拥有)盒子提供了 Value 语义 . 在内部,它们使用原始指针实现,就像几乎任何其他高级抽象一样 .

    Box es(事实上,对于几乎所有其他智能指针都是如此,例如 Rc )也可以借用:你可以从 Box<T> 获得 &T . 这可以通过 . 运算符自动执行,也可以通过解除引用并再次引用它来显式执行:

    let x: Box<u32> = Box::new(12);
    let y: &u32 = &*x;
    

    在这方面, Box es类似于内置指针 - 您可以使用取消引用运算符来访问其内容 . 这是可能的,因为Rust中的dereference运算符是可重载的,并且它对于大多数(如果不是全部)智能指针类型都是重载的 . 这允许轻松借用这些指针内容 .

    最后, ref 只是模式中的语法,用于获取引用类型的变量而不是值 . 例如:

    let x: u32 = 12;
    
    let y = x;           // y: u32, a copy of x
    let ref z = x;       // z: &u32, points to x
    let ref mut zz = x;  // zz: &mut u32, points to x
    

    虽然上面的例子可以用引用运算符重写:

    let z = &x;
    let zz = &mut x;
    

    (这也会使它更具惯用性),有些情况下 ref 是必不可少的,例如,当引用枚举变体时:

    let x: Option<Vec<u32>> = ...;
    
    match x {
        Some(ref v) => ...
        None => ...
    }
    

    在上面的示例中, x 仅在整个 match 语句中借用,该语句允许在 match 之后使用 x . 如果我们这样写:

    match x {
        Some(v) => ...
        None => ...
    }
    

    然后 x 将被此 match 消耗,并且在它之后将变得无法使用 .

  • 79

    Box 在逻辑上是原始指针( *const T )周围的新类型 . 但是,它在构造和销毁期间分配和释放其数据,因此不必从其他来源借用数据 .

    其他指针类型也是如此,例如 Rc - 引用计数指针 . 这些是包含私有原始指针的结构,它们分配给它们并从中释放出来 .

    原始指针与普通指针具有完全相同的布局,因此在某些情况下与C指针不兼容 . 重要的是, *const str*const [T] 是胖指针,这意味着它们包含有关值的长度的额外信息 .

    但是,原始指针绝对不能保证其有效性 . 例如,我可以安全地做

    123 as *const String
    

    该指针无效,因为内存位置 123 未指向有效的 String . 因此,当解除引用时,需要 unsafe 块 .

    此外,虽然借款必须遵守某些法律 - 即如果一个是可变的,你就不能有多次借款 - 原始指针不必尊重这一点 . There are other, weaker, laws that must be obeyed,但你不太可能与这些人发生冲突 .

    *mut*const 之间没有逻辑上的区别,尽管它们可能需要被转换为另一个来执行某些操作 - 差异是记录性的 .

  • 4

    引用和原始指针在实现级别是相同的 . 与程序员的观点不同的是,引用是安全的(在Rust术语中),但原始指针不是 .

    借用检查器保证引用始终有效(终身管理),您可以在时间上只有一个可变引用,等等 .

    这些类型的约束对于许多用例来说可能过于严格,因此原始指针(没有任何约束,如在C / C中)对于实现低级数据结构以及通常低级别的东西很有用 . 但是,您只能在 unsafe 块内取消引用原始指针或对它们执行操作 .

    标准库中的容器也使用原始指针 BoxRc 实现 .

    BoxRc 是C中的智能指针,即原始指针的包装器 .

相关问题