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

zrn*_*ely 49 pointers rust

我对Rust中指针的工作方式感到有些困惑.有ref,Box,&,*,我不知道他们是如何协同工作.

这是我目前的理解方式:

  1. Box 它实际上不是一个指针 - 它是一种在堆上分配数据的方法,并在函数参数中传递未经过类型化的特性(特别是特征).
  2. 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
    
    Run Code Online (Sandbox Code Playgroud)
  3. &用来借(借来的指针).如果我有一个函数,fn foo(&self)那么我正在引用自己的函数将在函数终止后过期,只留下调用者的数据.我也可以传递我想要保留所有权的数据bar(&mydata).

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

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

Vla*_*eev 90

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

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

&T        - immutable (shared) reference
&mut T    - mutable (exclusive) reference

*const T  - immutable raw pointer
*mut T    - mutable raw pointer
Run Code Online (Sandbox Code Playgroud)

最后两个之间的差异很小,因为任何一个都可以在没有任何限制的情况下被转换为另一个,因此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;
Run Code Online (Sandbox Code Playgroud)

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

*ref1; *ref2;

unsafe { *raw1; *raw2; }
Run Code Online (Sandbox Code Playgroud)

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

struct X { n: u32 };

impl X {
    fn method(&self) -> u32 { self.n }
}
Run Code Online (Sandbox Code Playgroud)

随后,尽管method()需要self通过参考,self.n自动解引用它,这样你就不必键入(*self).n.method()调用时会发生类似的事情:

let x = X { n: 12 };
let n = x.method();
Run Code Online (Sandbox Code Playgroud)

在这里,编译器自动引用xx.method(),这样你就不用写(&x).method().

接下来的最后一段代码也演示了特殊的&self语法.这仅仅意味着self: &Self,或者更具体地说,self: &X在这个例子中.&mut self,*const self,*mut self也行.

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

Rust还具有动态大小(或未大小)的类型.这些类型没有明确的静态已知大小,因此只能通过指针/引用使用.但是,只有指针是不够的 - 需要额外的信息,例如,切片的长度或指向特征对象的虚拟方法表的指针.这些信息被"嵌入"指向未经过类型化的指针,使这些指针"变胖".

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

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

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

BoxES(其实,这是几乎所有其他智能指针的真实,像Rc)也可借:你可以得到一个&T出来的Box<T>.这可以通过.操作员自动进行,也可以通过解除引用并再次引用它来显式执行:

let x: Box<u32> = Box::new(12);
let y: &u32 = &*x;
Run Code Online (Sandbox Code Playgroud)

在这方面,Boxes类似于内置指针 - 您可以使用解引用运算符来访问其内容.这是可能的,因为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
Run Code Online (Sandbox Code Playgroud)

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

let z = &x;
let zz = &mut x;
Run Code Online (Sandbox Code Playgroud)

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

let x: Option<Vec<u32>> = ...;

match x {
    Some(ref v) => ...
    None => ...
}
Run Code Online (Sandbox Code Playgroud)

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

match x {
    Some(v) => ...
    None => ...
}
Run Code Online (Sandbox Code Playgroud)

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

  • @AlexM.,不,比较是正确的。`Some(&amp;v)` 与 `Some(v)` 或 `Some(ref v)` 完全不同——它只适用于 `Option&lt;&amp;T&gt;`,并且它会尝试取消引用保存在 `Some` 中的引用并分配 `v` 取消引用的值。换句话说,`Some(&amp;v)` 与 `Some(ref v)` 正好相反。 (4认同)
  • 很棒的答案,谢谢。我能够自己做更多的研究,我发现重载解引用操作的方法是实现`Deref` trait。(把这个留在这里给任何阅读答案并好奇的人)。 (3认同)
  • 关于最后几段,你声称在某些情况下 `ref` 是必不可少的,我想应该将 `Some(ref v) =&gt; ...` 行与 `Some(&amp; v) =&gt; 行进行比较。 ..`,而不是`Some(v) =&gt; ...`(就像你所做的那样)。换句话说,`&amp;` *总是* 是 `ref` 的同义词(因此允许删除 `ref`),还是它们有不同的用例? (3认同)
  • @VladimirMatveev 为什么它会尝试取消引用并分配给“v”?这样就可以在 Some 块中访问它的实际值? (2认同)

Moh*_*HME 13

虽然有点晚了但可能会有所帮助。

\n

一张桌子

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n
参考/
指针
数据
位置
可变的共享
所有权
安全的实现
复制
与T\xe2\x9d\x8c\xe2\x9c\x94\xef\xb8\x8f\xef\xb8\x8f\xe2\x9c\x94\xef\xb8\x8f\xe2\x9c\x94\xef\xb8\x8f
&mutT\xe2\x9c\x94\xef\xb8\x8f\xe2\x9d\x8c\xe2\x9c\x94\xef\xb8\x8f\xe2\x9d\x8c
*常量T\xe2\x9d\x8c\xe2\x9c\x94\xef\xb8\x8f\xe2\x9d\x8c\xe2\x9c\x94\xef\xb8\x8f
*突变 T\xe2\x9c\x94\xef\xb8\x8f\xe2\x9c\x94\xef\xb8\x8f\xe2\x9d\x8c\xe2\x9c\x94\xef\xb8\x8f
盒子<T>\xe2\x9c\x94\xef\xb8\x8f\xe2\x9d\x8c\xe2\x9c\x94\xef\xb8\x8f\xe2\x9d\x8c
RC<T>\xe2\x9d\x8c\xe2\x9c\x94\xef\xb8\x8f\xe2\x9c\x94\xef\xb8\x8f\xe2\x9d\x8c
\n

B. 表中的评论

\n

与T

\n
    \n
  • Mutable (\xe2\x9d\x8c): Error :无法赋值给*some_ref,它后面的&引用some_ref&引用,所以它引用的数据不能写入rustc (E0594)
  • \n
  • 共享(\xe2\x9c\x94\xef\xb8\x8f)
  • \n
  • 安全(\xe2\x9c\x94\xef\xb8\x8f)
  • \n
  • 实现复制(\xe2\x9c\x94\xef\xb8\x8f)
  • \n
\n

&mutT

\n
    \n
  • 可变(\xe2\x9c\x94\xef\xb8\x8f)
  • \n
  • 共享(\xe2\x9d\x8c):只有一个所有者。错误:不能x一次多次借用可变借用,第二次可变借用发生在rustc (E0499)处。
  • \n
  • 安全(\xe2\x9c\x94\xef\xb8\x8f)
  • \n
  • impl Copy (\xe2\x9d\x8c):错误:发生移动是因为some_refhas type &mut u32,它没有实现该Copy特征。
  • \n
\n

*常量T

\n
    \n
  • Mutable : (\xe2\x9d\x8c): Error :无法赋值给,指针*some_raw_pointer后面是指针,因此它引用的数据无法写入rustc (E0594)*constraw1*const
  • \n
  • 共享(\xe2\x9c\x94\xef\xb8\x8f)
  • \n
  • 安全:(\xe2\x9d\x8c):错误:取消引用原始指针是不安全的,需要不安全的函数或块原始指针可能为空、悬空或未对齐;它们可能违反别名规则并导致数据竞争:所有这些都是未定义的行为rustc (E0133)
  • \n
  • impl Copy (\xe2\x9c\x94\xef\xb8\x8f):请查看官方文档
  • \n
\n

*突变T

\n
    \n
  • 可变(\xe2\x9c\x94\xef\xb8\x8f)
  • \n
  • 共享(\xe2\x9c\x94\xef\xb8\x8f)
  • \n
  • 安全(\xe2\x9d\x8c):错误:取消引用原始指针是不安全的,需要不安全的函数或块\n原始指针可能为空、悬空或未对齐;它们可能违反别名规则并导致数据竞争:所有这些都是未定义的行为rustc (E0133)
  • \n
  • impl Copy (\xe2\x9c\x94\xef\xb8\x8f):请查看官方文档
  • \n
\n

盒子<T>

\n
    \n
  • 可变(\xe2\x9c\x94\xef\xb8\x8f)
  • \n
  • 共享(\xe2\x9d\x8c):为了证明这一点,在某个范围内使用对某个框的引用,该引用将在该范围结束后立即删除,因为它只有一个所有者。请参阅此SO 答案以获取更多详细信息。错误some_box寿命不够长借用的价值寿命不够长rustc (E0597)
  • \n
  • 安全的(\xe2\x9c\x94\xef\xb8\x8f)
  • \n
  • impl Copy (\xe2\x9d\x8c):请查看官方文档。其实是有原因的
  • \n
\n
\n

您无法实现 Box 的复制,这将允许创建引用同一事物的多个框。

\n
\n

RC<T>

\n
    \n
  • 可变(\xe2\x9d\x8c):嗯,只有一个副本是可变的,而且有点复杂。错误:无法分配给Rc特征中的数据DerefMut,需要通过取消引用进行修改,但Rc<u32> rustc 没有实现它(E0594)
  • \n
  • 共享(\xe2\x9c\x94\xef\xb8\x8f):实际上是多重所有权。
  • \n
  • 安全(\xe2\x9c\x94\xef\xb8\x8f)
  • \n
  • impl Copy (\xe2\x9d\x8c):请查看官方文档
  • \n
\n
\n

C. 相关说明

\n

1. 复制特质与移动

\n

根据官方文档

\n
\n

需要注意的是,在这两个示例中,\xe2\x80\x99 唯一的区别是赋值后是否允许访问 x。在幕后,复制和移动都可能导致位被复制到内存中,尽管有时会对此进行优化。

\n
\n

因此,请注意move转让所有权,同时Copy无关。

\n

2. 可变引用不实现复制

\n
\n

某些类型无法安全复制\xe2\x80\x99。例如,复制 &mut T 将创建一个别名可变引用。复制 String 会重复管理 String\xe2\x80\x99s 缓冲区的责任,从而导致双重释放。

\n
\n

无论如何,阅读全文还是有好处的Copy

\n

3.取消引用指针和不安全

\n

这里的术语unsafe意味着你将无法取消引用指针,除非使用unsafe。否则,您将收到以下错误:

\n
\n

原始指针的取消引用是不安全的,需要不安全的函数或块原始指针可能为空、悬空或未对齐;它们可能违反别名规则并导致数据竞争:所有这些都是未定义的行为rustc (E0133)

\n
\n

4.ref与相同&

\n


Vee*_*rac 9

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

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

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

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

123 as *const String
Run Code Online (Sandbox Code Playgroud)

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

此外,虽然借款必须遵守某些法律 - 即如果一个是可变的,你就不能有多次借款 - 原始指针不必尊重这一点.还有其他较弱的法则必须遵守,但你不太可能与这些法律发生冲突.

*mut和之间没有逻辑上的区别*const,尽管它们可能需要被铸造到另一个以进行某些操作 - 差异是具有记录性的.


eul*_*isk 5

在实现级别,引用和原始指针是同一回事。从程序员的角度来看,区别在于引用是安全的(以Rust术语而言),而原始指针则不是。

借阅检查器保证引用始终有效(生命周期管理),一次只能有一个可变引用等等。

这些约束类型对于许多用例而言可能过于严格,因此原始指针(如C / C ++中没有任何约束)对于实现低级数据结构以及一般的低级内容很有用。但是,您只能取消引用原始指针或在unsafe块内对其进行操作。

在标准库容器使用裸指针,实行BoxRc太。

BoxRc什么是智能指针是C ++,也就是围绕原始指针包装。