所属权是rust最有特色的功能,该功能使得rust无需垃圾回收机制即可保证内存安全。那么所属权是啥以及有啥用呢?
首先,rust中的所属权含义:
1、所属权规则:
(1)、Rust中的每个值都有一个变量,称为其所有者。
(2)、一次只能是一个所有者。
(3)、当所有者超出作用范围时,该值将被删除。
2、什么是作用范围:作用范围类似于:{let s = "hello";}
3、内存和分配:
在rust中,当拥有内存的变量超出范围后,内存将自动释放,例如:
fn main() {
{
// 从现在开始,s是有效的
let s = String::from("hello");
//用s做一些操作
}
// 此作用域现已结束,并且s不再有效
}
**: 当变量超出范围时,Rust为我们调用一个特殊函数。 此函数称为drop,它是String的创建者(以上例进行说明)可以在其中放置代码以返回内存的地方。 Rust会自动在右大括号关闭除调用。
*变量和数据交互的方式:移动
多个变量可以在Rust中以不同的方式与同一数据交互。例如:
fn main() {
let x = 5;
let y = x;
}
//“将值5绑定到x; 然后在x中复制值并将其绑定到y。” 现在,我们有两个变量x和y,它们都等于5。这种情况是确确实实存在的,因为整数是具有已知固定大小的简单值,并且这5个值被压入堆栈。
再来看看String类型的:
fn main() {
let s1 = String::from("hello");
let s2 = s1;
}
//这看起来与先前的代码非常相似,因此我们可以假设它的工作方式是相同的:也就是说,第二行将在s1中复制该值并将其绑定到s2。 但这不是完全会发生的事情。
// 字符串由三部分组成:指向内存的指针,用于保存字符串的内容,长度和容量。 这组数据存储在堆栈中。
//长度是String当前正在使用的内存量(以字节为单位)。 容量是String从分配器接收的内存总量(以字节为单位)。 长度和容量之间的差异很重要,但在这种情况下并不重要,因此,暂时可以忽略容量。
//当我们将s1分配给s2时,将复制String数据,这意味着我们将复制堆栈上的指针,长度和容量。 我们不将数据复制到指针引用的堆上。
//如果Rust代替复制了堆数据,那么如果堆上的数据很大,则运行时性能s2 = s1可能会非常昂贵。
//当变量超出范围时,Rust自动调用drop函数并清除该变量的堆内存。 但是如果存在两个指向相同位置的数据指针。 这是一个问题:当s2和s1超出范围时,它们都将尝试释放相同的内存。 这被称为双重释放错误,是内存安全错误之一。 两次释放内存可能导致内存损坏,从而可能导致安全漏洞。
确保数据安全:
为了确保内存安全,Rust中出现了这种情况的详细信息。 Rust不会尝试复制分配的内存,而是认为s1不再有效,因此,当s1超出范围时,Rust不需要释放任何内容。 检查创建s2之后尝试使用s1会发生什么情况; 它不起作用:
fn main() {
let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1);
}
//let s1 = String::from("hello");
| -- `move occurs because `s1` has type `std::string::String`, which does not implement the `Copy` trait`
//`let s2 = s1;`
`-- value moved here`
//` println!("{}, world!", s1);`
`^^ value borrowed here after move`
在js中有浅复制与深复制的概念。 但是由于Rust也使第一个变量无效,而不是被称为浅表副本,因此被称为移动。 在此示例中,我们说s1已移入s2。这也就是rust中移动的概念
这就说明了上面出现的问题! 只有s2有效,当它超出范围时,仅凭它就可以释放内存,我们就大功告成了。
此外,这暗示了一种设计选择:Rust永远不会自动创建数据的“深层”副本。 因此,就运行时性能而言,任何自动复制都可以被认为是廉价的。
变量与数据交互的方式:克隆
如果我们确实想深入复制String的堆数据,而不仅仅是堆栈数据,则可以使用一种称为clone的通用方法,例如:
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
}
//此时s1仍旧是起作用的,它的值只不过是被复制了,但是其所指向的存储空间并没有被复制。
仅堆栈数据:复制
先来看一个例子:
fn main() {
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y);
}
//这段代码所揭示的含义是:
//诸如在编译时具有已知大小的整数之类的类型完全存储在堆栈中,因此可以快速制作实际值的副本。 这意味着在创建变量y之后我们没有理由要阻止x生效。 换句话说,这里的深层复制和浅层复制没有什么区别,因此调用克隆与通常的浅层复制没有什么不同,我们可以省去克隆操作。
Rust具有一个特殊的注释,称为复制特征,我们可以将其放置在存储在堆栈中的整数之类的类型上。 如果类型具有“复制”特征,则分配后仍然可以使用较旧的变量。 如果该类型或其任何部分实现了Drop特性,Rust将不允许我们使用Copy特性对该类型进行注释。 如果在值超出范围时该类型需要特殊处理,并向该类型添加Copy批注,则会出现编译时错误。
那么复制是什么类型? 作为一般规则,任何一组简单的标量值都可以是Copy,而不需要分配或某种形式的资源的任何内容都可以是Copy。 以下是一些“复制”类型:
* 所有整数类型,例如u32。
* 布尔型布尔值,值为true和false。
* 所有浮点类型,例如f64。
* 字符类型,`char`。
* 元组(如果它们仅包含也是可以Copy的类型)。 例如,`(i32, i32)`是Copy,但是`(i32,String)`不是。
所有权和函数:
用于将值传递给函数的语义类似于用于将值分配给变量的语义。 就像赋值一样,将变量传递给函数将移动或复制。 下面是一个带有一些注释的示例,该注释显示了变量进入和超出作用域的位置:
fn main() {
let s = String::from("hello"); // s进入作用域
takes_ownership(s); // s的值移入函数...
// ...因此在这里及其之后不再有效
let x = 5; // x进入作用域
makes_copy(x); // x将移入函数,
// 但是i32是Copy,所以还可以在这之后使用x
} // 在此,x超出作用域,然后是s。 但是因为s的值被移动了,所以没有什么特别的事情发生。
fn takes_ownership(some_string: String) { // some_string进入作用域
println!("{}", some_string);
} // 在这里,some_string超出作用域并调用`drop`。 备份内存已释放。
fn makes_copy(some_integer: i32) { // some_integer进入作用域
println!("{}", some_integer);
} // 在这里, some_integer超出作用域。没什么特别的事情发生。
返回值和作用域
返回值也可以转移所有权,例如:
fn main() {
let s1 = gives_ownership(); // gives_ownership将其返回值移动至s1.
let s2 = String::from("hello"); // s2进入作用域
let s3 = takes_and_gives_back(s2); // s2被移入takes_and_gives_back,它的返回值也移入s3
} // 在此,s3超出作用域并被释放内存。 s2超出作用域但被移动了,所以什么也没发生。 s1超出作用域并被释放内存。
fn gives_ownership() -> String { // gets_ownership会将其返回值移至调用它的函数中
let some_string = String::from("hello"); // some_string进入作用域
some_string // some_string被返回并且移至调用gives_ownership的函数中
}
// take_and_gives_back将获取一个String并返回一个
fn takes_and_gives_back(a_string: String) -> String { // a_string进入作用域
a_string // a_string被返回并且移至调用takes_and_gives_back的函数中
}
变量的所有权每次都遵循相同的模式:将值分配给另一个变量将其移动。 当包含堆上数据的变量超出作用域时,将删除该值,除非该数据已被移交给另一个变量拥有。
拥有所有权然后返回所有功能的所有权有点乏味。 如果我们想让函数使用值而不是所有权怎么办? 令人十分烦恼的是,除了我们可能还想返回的函数主体所产生的任何数据之外,如果我们想再次使用它们,还需要将其传递回去。
可以使用元组返回多个值,例如:
fn main() {
let s1 = String::from("hello");
let (s2, len) = calculate_length(s1);
println!("The length of '{}' is {}.", s2, len);
}
fn calculate_length(s: String) -> (String, usize) {
let length = s.len(); // len() 返回一个字符串的长度
(s, length)
}
但这对于一个应该很普遍的概念来说是太多的形式和大量的工作。 对我们来说幸运的是,Rust具有此概念的功能,称为参考。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。