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

jhiyze9q  于 2022-11-12  发布在  其他
关注(0)|答案(4)|浏览(429)

我对Rust中指针的工作方式有点困惑,有refBox&*,我不确定它们是如何一起工作的。
以下是我目前的理解:

  1. Box并不是一个真正的指针--它是一种在堆上分配数据的方式,并在函数参数中传递未确定大小的类型(尤其是trait)。
  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
  1. &用于进行借用(借用指针)。如果我有一个函数fn foo(&self),那么我将获取一个对我自己的引用,该引用将在函数终止后过期,而不影响调用者的数据。我还可以通过bar(&mydata)传递我希望保留所有权的数据。
  2. *用于生成原始指针:例如,let y: i32 = 4; let x = &y as *const i32。我理解C/C++中的指针,但我不确定它如何与Rust的类型系统一起工作,以及如何安全地使用它们。我也不确定这种类型的指针的用例是什么。另外,*符号可以用于解引用事物(什么事物,为什么?)。
    有人能给我解释一下第四种类型的指针吗,并验证我对其他类型的理解是正确的吗?我也希望有人能指出我没有提到的任何常见用例。
a11xaf1n

a11xaf1n1#

首先,您列出的所有项实际上都是不同的东西,即使它们与指针相关。Box是库定义的智能指针类型; ref是用于模式匹配的语法; &是一个引用运算符,在引用类型中兼作符号; *是一个解引用运算符,在原始指针类型中也是一个符号。请参见下面的详细说明。
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。这些类型没有明确的静态已知大小,因此只能通过指针/引用来使用。然而,只有指针是不够的--还需要额外的信息,例如切片的长度或trait对象的虚拟方法表的指针。这些信息“嵌入”在指向无大小类型的指针中,使这些指针变得“胖”。
胖指针基本上是一种结构,它包含指向数据块的实际指针和一些附加信息(切片的长度,trait对象的vtable指针)这里重要的是Rust对用户完全透明地处理指针内容的细节--如果你传递&[u32]*mut SomeTrait值,相应的内部信息将被自动沿着。
Box<T>是Rust标准库中的智能指针之一,它提供了一种在堆上分配足够内存的方法,以存储相应类型的值,然后它充当句柄,即指向该内存的指针。Box<T>拥有它所指向的数据;当它被删除时,堆上相应的内存块被解除分配。
一个非常有用的方法是把它们看作是固定大小的常规值,也就是说,Box<T>等价于T,只是它总是取一些与机器指针大小相对应的字节。我们说(拥有的)盒子提供了 * 值语义 *。在内部,它们使用原始指针实现,就像几乎任何其他高级抽象一样。
Box es(事实上,这对于几乎所有其他智能指针都是正确的,例如Rc)也可以被借用:你可以从Box<T>中得到一个&T。这可以通过.运算符自动实现,或者你也可以通过解引用然后再次引用它来显式实现:

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

在这方面,Box es类似于内置指针--你可以使用解引用操作符来访问它们的内容。这是可能的,因为Rust中的解引用操作符是可重载的,并且它对于大多数(如果不是全部)智能指针类型都是重载的。这允许轻松借用这些指针的内容。
最后,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消耗,并且在它之后将变得不可用。

wgmfuz8q

wgmfuz8q2#

Box在逻辑上是原始指针(*const T)周围的新类型。但是,它在构造和销毁期间分配和释放其数据,因此不必从其他源借用数据。
其他指针类型也是如此,比如Rc--一个引用计数指针。这些结构体包含私有的原始指针,它们可以分配给这些指针,也可以从这些指针中释放。
原始指针的布局与普通指针完全相同,因此在某些情况下与C指针不兼容。重要的是,*const str*const [T]是 * 胖指针 *,这意味着它们包含关于值长度的额外信息。
然而,原始指针并不能保证它们的有效性。

123 as *const String

该指针无效,因为存储器位置123未指向有效的String。因此,当解引用一个String时,需要unsafe块。
此外,尽管借入需要遵守某些定律--即如果一个借入是可变的,就不能有多个借入--但原始指针不必遵守这一点。还有其他一些必须遵守的、较弱的定律,但你不太可能与这些定律发生冲突。
*mut*const之间没有逻辑差异,尽管它们可能需要转换为另一个来执行某些操作-差异是文档性的。

tp5buhyn

tp5buhyn3#

引用和原始指针在实现层面上是一样的,从程序员的Angular 来看,不同之处在于引用是安全的(用Rust的术语来说),而原始指针不是。
借用检查器保证引用总是有效的(生存期管理),一次只能有一个可变引用,等等。
这些类型的约束对于很多用例来说可能过于严格,所以原始指针(没有任何约束,如C/C++)对于实现低级数据结构和一般的低级内容是有用的。但是,您只能在unsafe块中解引用原始指针或对它们进行操作。
标准库中的容器也是使用原始指针BoxRc实现的。
BoxRc就是C++中的智能指针,也就是原始指针的 Package 器。

ergxz8rk

ergxz8rk4#

我想补充我的两点意见。

A.table

| 参考文件/指标|数据类型位置|易变|共享所有权|保险箱|实现复制|
| - -|- -|- -|- -|- -|- -|
|与T| 堆栈|❌| ✔️️ |✔️ |✔️|
|静音T(& M)| 堆栈|✔️ |❌| ✔️ |❌|
|*常数T| 堆栈|❌| ✔️ |❌| ✔️|
|*静音T| 堆栈|✔️ |✔️ |❌| ✔️|
|方块 | 堆|✔️ |❌| ✔️ |❌|
|接收器 | 堆|❌| ✔️ |✔️ |❌|

B.表格注解

和T

*可变():错误:无法指派给*some_ref,因为它位于&指涉之后。some_ref&指涉,所以它所指涉的数据无法写入 rustc(E0594)
*共享(️)
*安全(️)
*实作副本(️)

&静音T

*可变(️)
*共享():只有一个所有者。错误:无法一次多次借用x作为可变对象,此处发生第二次可变借用 rustc(E0499)
*安全(️)
*实施副本():错误:由于some_ref具有类型&mut u32而发生移动,该类型未实现Copy特征。

* 常数T

*可变:():错误:无法为*some_raw_pointer赋值,因为*some_raw_pointer位于*const指针之后。raw1*const指针,因此无法写入它所引用的数据 rustc(E0594)
*共享(️)
*安全:():错误:取消引用原始指针是不安全的,需要不安全的函数或块原始指针可能为空、悬空或未对齐。它们可能违反别名规则并导致数据争用:所有这些都是未定义行为 rustc(E0133)
*impl Copy(️):请检查官方文件。

* 静音T

*可变(️)
*共享(️)
*安全():错误:取消引用原始指针是不安全的,需要不安全的函数或块原始指针可能为空、悬空或未对齐。它们可能违反别名规则并导致数据争用:所有这些都是未定义行为 rustc(E0133)
*impl副本(️):请检查官方文件。

Box(第一个字母)

*可变(️)
*共享():为了证明这一点,请在某个范围内使用对某个框的引用,该引用将在该范围结束后立即删除,因为它只有一个所有者。有关详细信息,请参阅此SO answer错误some_box的有效期不够长借用值的有效期不够长 rustc(E0597)
*安全(️)
*实施副本():请检查Official Documentation.实际上有一个reason

您不能实现Copy for Box,这将允许创建多个引用同一事物的框。

Rc(一个字母)

*可变():只有一个副本是可变的,而且它有点复杂。错误:无法指定给Rc trait中的数据,必须透过取消指涉来修改DerefMut,但未针对Rc<u32>rustc(E0594) 实作。
*共享(⇒️):实际上是多重所有权。
*安全(️)
*实施副本():请检查Official Documentation

C.相关说明

1.复制特征与移动

根据官方文件:
值得注意的是,在这两个例子中,唯一的区别是在赋值后是否允许访问x。实际上,复制和移动都可能导致位被复制到内存中,尽管有时会优化掉。
因此,请注意,move会转移所有权,而Copy与此无关。

2.可变引用不实现复制

有些类型不能安全地复制。例如,复制&mut T会创建一个别名可变引用。复制String会重复管理String缓冲区的责任,导致双重释放。
阅读完整的Copy文档页面是很好的。

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

这里的术语unsafe意味着除非使用unsafe函数或块,否则你将无法解引用指针。否则,你将得到以下错误:
原始指针解引用是不安全的,且需要不安全的函数或块原始指针可能是空的、悬空的或未对齐的;它们可能违反别名规则并导致数据争用:所有这些都是未定义行为 rustc(E0133)

4. ref&相同

相关问题