我对Rust中指针的工作方式有点困惑,有ref
,Box
,&
,*
,我不确定它们是如何一起工作的。
以下是我目前的理解:
Box
并不是一个真正的指针--它是一种在堆上分配数据的方式,并在函数参数中传递未确定大小的类型(尤其是trait)。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)
,那么我将获取一个对我自己的引用,该引用将在函数终止后过期,而不影响调用者的数据。我还可以通过bar(&mydata)
传递我希望保留所有权的数据。*
用于生成原始指针:例如,let y: i32 = 4; let x = &y as *const i32
。我理解C/C++中的指针,但我不确定它如何与Rust的类型系统一起工作,以及如何安全地使用它们。我也不确定这种类型的指针的用例是什么。另外,*
符号可以用于解引用事物(什么事物,为什么?)。
有人能给我解释一下第四种类型的指针吗,并验证我对其他类型的理解是正确的吗?我也希望有人能指出我没有提到的任何常见用例。
4条答案
按热度按时间a11xaf1n1#
首先,您列出的所有项实际上都是不同的东西,即使它们与指针相关。
Box
是库定义的智能指针类型;ref
是用于模式匹配的语法;&
是一个引用运算符,在引用类型中兼作符号;*
是一个解引用运算符,在原始指针类型中也是一个符号。请参见下面的详细说明。Rust中有四种基本的指针类型,它们可以分为两组-引用和原始指针:
最后两个变量之间的差别很小,因为任何一个变量都可以不受任何限制地被强制转换为另一个变量,所以
const
/mut
的区别主要是作为一个lint。原始指针可以自由地创建到任何对象,例如,它们也可以从整数中凭空创建。当然,引用就不是这样了--引用类型和它们的交互定义了Rust的一个关键特性:借用。引用在创建的方式和时间、使用的方式以及相互之间的交互方式等方面有很多限制。反过来,它们可以在没有
unsafe
块的情况下使用。但借用究竟是什么以及它是如何工作的不在本答案的讨论范围之内。引用和原始指针都可以使用
&
运算符创建:引用和原始指针都可以使用
*
运算符解除引用,尽管对于原始指针,它需要一个unsafe
块:解引用运算符经常被省略,因为另一个运算符“点”(dot)运算符(即
.
)会自动引用或解引用它的左参数。然后,尽管
method()
通过引用获取self
,self.n
会自动取消引用它,因此您不必键入(*self).n
。当调用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
。这可以通过.
运算符自动实现,或者你也可以通过解引用然后再次引用它来显式实现:在这方面,
Box
es类似于内置指针--你可以使用解引用操作符来访问它们的内容。这是可能的,因为Rust中的解引用操作符是可重载的,并且它对于大多数(如果不是全部)智能指针类型都是重载的。这允许轻松借用这些指针的内容。最后,
ref
只是模式中的一个语法,用于获取引用类型的变量而不是值。例如:虽然上面的例子可以用引用运算符重写:
(这也会使它更符合习惯用法),但在某些情况下,
ref
是必不可少的,例如,当引用枚举变量时:在上面的例子中,
x
只在整个match
语句中被借用,这允许在这个match
之后使用x
。如果我们这样写:则
x
将被这个match
消耗,并且在它之后将变得不可用。wgmfuz8q2#
Box
在逻辑上是原始指针(*const T
)周围的新类型。但是,它在构造和销毁期间分配和释放其数据,因此不必从其他源借用数据。其他指针类型也是如此,比如
Rc
--一个引用计数指针。这些结构体包含私有的原始指针,它们可以分配给这些指针,也可以从这些指针中释放。原始指针的布局与普通指针完全相同,因此在某些情况下与C指针不兼容。重要的是,
*const str
和*const [T]
是 * 胖指针 *,这意味着它们包含关于值长度的额外信息。然而,原始指针并不能保证它们的有效性。
该指针无效,因为存储器位置
123
未指向有效的String
。因此,当解引用一个String
时,需要unsafe
块。此外,尽管借入需要遵守某些定律--即如果一个借入是可变的,就不能有多个借入--但原始指针不必遵守这一点。还有其他一些必须遵守的、较弱的定律,但你不太可能与这些定律发生冲突。
*mut
和*const
之间没有逻辑差异,尽管它们可能需要转换为另一个来执行某些操作-差异是文档性的。tp5buhyn3#
引用和原始指针在实现层面上是一样的,从程序员的Angular 来看,不同之处在于引用是安全的(用Rust的术语来说),而原始指针不是。
借用检查器保证引用总是有效的(生存期管理),一次只能有一个可变引用,等等。
这些类型的约束对于很多用例来说可能过于严格,所以原始指针(没有任何约束,如C/C++)对于实现低级数据结构和一般的低级内容是有用的。但是,您只能在
unsafe
块中解引用原始指针或对它们进行操作。标准库中的容器也是使用原始指针
Box
和Rc
实现的。Box
和Rc
就是C++中的智能指针,也就是原始指针的 Package 器。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
与&
相同