借用和引用

文章目录

所有权

Rust通过所有权来管理内存,最妙的是,这种检查只发生在编译期,因此对于程序运行期,不会有任何性能上的损失。

使用堆和栈的性能区别:

写入方面:入栈比在堆上分配内存要快。 因为入栈时操作系统无需分配新的空间,只需要将新数据放入栈顶即可。相比之下,在堆上分配内存则需要更多的工作,这是因为操作系统必须首先找到一块足够存放数据的内存空间,接着做一些记录为下一次分配做准备。

读取方面:出栈比读取堆上的数据快。 栈数据往往可以直接存储在 CPU 高速缓存中,而堆数据只能存储在内存中。访问堆上的数据比访问栈上的数据慢,因为必须先访问栈再通过栈上的指针来访问内存。

因此,处理器处理分配在栈上数据会比在堆上的数据更加高效。

Rust的所有权原则:

  1. Rust 中每一个值都被一个变量所拥有,该变量被称为值的所有者
  2. 一个值同时只能被一个变量所拥有,或者说一个值只能拥有一个所有者
  3. 当所有者(变量)离开作用域范围时,这个值将被丢弃(drop)

简单说就是每一个值(堆上的值)有且只有一个所有者(变量),当这个变量出了作用域,那么这个值也被丢弃(在堆上也会丢弃)。

看一段代码:

rust 复制代码
fn main(){
    let x: &str = "hello world";
    let y = x;
    println!("{},{}",x,y);
}

这段代码并不会报错,这是因为 x 在这里只是引用了字符串,没有所有权,所以 let y = x; 是对引用的拷贝,不会有所有权的转移(具体看下一节 "引用与借用" )

函数的传参和返回:

传参:

rust 复制代码
fn main() {
    let s = String::from("hello");  // s 进入作用域
    takes_ownership(s);             // 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 移出作用域。不会有特殊操作

返回:

rust 复制代码
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 {  
    let some_string = String::from("hello"); // some_string 进入作用域.
    some_string                              // 返回 some_string 并移出给调用的函数
}

// takes_and_gives_back 将传入字符串并返回该值
fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用域
    a_string  // 返回 a_string 并移出给调用的函数
}

引用和借用

在 Rust 中,获取变量的引用叫做借用

引用:&x,解引用:*x;

用在函数参数和函数返回值:

rust 复制代码
fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1);
    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

可变引用

引用默认是不能修改值的,想要修改值需要使用可变引用:

rust 复制代码
fn main() {
    let mut s = String::from("hello");
    change(&mut s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

要注意的是,特定数据的可变引用在同一个作用域只能存在一个,并且,可变引用和不可变引用不能同时存在。

新旧编译器的引用作用域不同,旧编译器(1.31之前)的引用作用域结束于最近的花括号处,而新编译器的引用作用域改变为 最后一次使用的位置。

这种优化行为叫做 Non-Lexical LifeTimes(NLL).

悬垂引用

指引用指向某个值后,值被释放掉,指针仍然存在的情况。

在 Rust 中编译器可以确保引用永远也不会变成悬垂状态:当你获取数据的引用后,编译器可以确保数据不会在引用结束前被释放,要想释放数据,必须先停止其引用的使用。

释放引用:

rust 复制代码
fn dangle() -> &String { // dangle 返回一个字符串的引用
    let s = String::from("hello"); // s 是一个新字符串
    &s // 返回字符串 s 的引用
} // 这里 s 离开作用域并被丢弃。其内存被释放。
  // 危险!

// 应该更改为
fn no_dangle() -> String {
    let s = String::from("hello");
    s
}
相关推荐
DongLi013 天前
rustlings 学习笔记 -- exercises/05_vecs
rust
番茄灭世神4 天前
Rust学习笔记第2篇
rust·编程语言
shimly1234564 天前
(done) 速通 rustlings(20) 错误处理1 --- 不涉及Traits
rust
shimly1234564 天前
(done) 速通 rustlings(19) Option
rust
@atweiwei4 天前
rust所有权机制详解
开发语言·数据结构·后端·rust·内存·所有权
shimly1234564 天前
(done) 速通 rustlings(24) 错误处理2 --- 涉及Traits
rust
shimly1234564 天前
(done) 速通 rustlings(23) 特性 Traits
rust
shimly1234565 天前
(done) 速通 rustlings(17) 哈希表
rust
shimly1234565 天前
(done) 速通 rustlings(15) 字符串
rust
shimly1234565 天前
(done) 速通 rustlings(22) 泛型
rust