Rust 05: 所有权机制详解 + move语义 + borrow借用语义

测试代码位于gitee仓库:https://gitee.com/linysuccess/rust0_100

通过前面几个小节的介绍,我们对Rust语言代码的样貌有了大致的印象。但其实,至今为止我们仍然停留在Rust写写简单代码的表象,如果不搞清楚Rust核心的所有权机制变量生命周期等问题,后续将很难深入。

这一节,我们就先来谈谈Rust中的“所有权”。

Rust所有权机制的设计是这门语言的最大特点,也是其内存安全多线程并发安全的基石。

1 定义struct Point

struct Point {
    x: i32,
    y: i32,
}

2 核心:一块内存在同一个时刻,只会有一个所有者

这就是Rust所有权机制的核心概念。

在核心概念之上,rust针对不同的使用场景衍生出了一些子规则:

  1. 一个值,在任何时候都只有一个变量对其拥有所有权。如果进行变量赋值,或者函数传参、从函数返回等,所有权默认情况会发生转移(move)
fn test_ownership() {
    // 只读的p1指向一个在栈上分配的Point结构
    let p1 = Point { x: 25, y: 25 };
    // 将p1指向的point的所有权转移给p2
    let p2: Point = p1;
    // p1已无效,只能通过p2进行访问
    // println!("{} {}", p2.x, p2.y);
}

​ 也有不发生所有权转移(move)的使用场景,比如基本的值类型i32、u8等。事实上是,如果一个类型实现了Copy trait,那么赋值、传参等操作的默认行为不是move所有权,而是复制一份新内容出来

let mut x = 1;
let y = 10;
// i32类型实现了Copy trait
// 这里的赋值,是复制行为
x = y;

// 打印y和x的地址,发现y和x地址不同
println!("{:p}", &y);//0xc4236fee5c
println!("{:p}", &x);//0xc4236fee58

// 通过复制,但两个地址上存放的值都是10
// 并且,y依然有自己的所有权
println!("y={}", y);//10
println!("x={}", x);//10
  1. 一个值除了有唯一的所有者之外,还可以有多个“借用(borrow)者”。借用者无须关心值内存的释放问题,也不必担心值内存已被释放(C++中悬垂指针)的问题。因为,编译器会确保对值的借用不会超出值本身的生命周期

    Rust中的借用类比C里面的引用,也用&符标注。只是为了突出rust中的这个“引用”不具有所有权,用“借用”表述更明确。

let p: Point = Point {x:1, y:1, };
// q是对p所指内容的借用
let mut q: &Point = &p;
// 所有权仍然归p
println!("p.x={}", p.x);
println!("q.x={}", q.x);
{
    // r指向栈上分配的Point数据
    let r = Point{x:10, y:10, };
    // 下面这句编译报错:borrowed value does not live long enough
    // r作为所有者,在花括号结束之后,生命周期就结束了
    // q的生命周期,要到main函数结束
    // q的生命周期更长,不能借用生命周期更短的r
    q = &r; 
}
println!("q.x={}", q.x);
  1. 当所有者离开作用域,其拥有的值被丢弃,内存得以释放

    因为单一所有权的约束,编译器能准确跟踪所有者的生命周期。从而,在所有者生命周期结束时,编译器自动插入释放代码完成堆内存的释放(实际是调用所有者实现的Drop trait中的方法)。

3 可写的借用不能与其他“活跃”借用共存

在一个作用域中,对同一个值的借用:

  • 要么都是只读借用;
  • 要么都不“活跃”,也就是说有借用但不加任何使用,当然这样没有意义;
  • 编译器不允许可写借用与其他“活跃”借用共存。
    下面,写几个小例子验证一下。
//代码片段1 多个只读借用,可以共存
let mut x = "Hello".to_string();
let x_b0 = &x;
let x_b1 = &x;
println!("{:?}", x_b0);
println!("{:?}", x_b1);

//代码片段2 有多个引用,但都不“活跃”,编译通过
let mut x = "Hello".to_string();
let x_b0 = &x;
let x_b1 = &x;
let mut x_b2 = &mut x;
let mut x_b3 = &mut x;

// 代码片段3 x_b0已经是“活跃”的只读借用,不允许再出现可写借用
let mut x = "Hello".to_string();
let x_b0 = &x;
let x_b1 = &x;
// 下面一行编译报错:
// cannot borrow `x` as mutable because it is also borrowed as immutable
let mut x_b2 = &mut x;
println!("{:?}", x_b0);

// 代码片段4  
// 只有1个活跃的可写借用,2个只读借用不活跃,编译通过
let mut x = "Hello".to_string();
let x_b0 = &x;
let x_b1 = &x;
let mut x_b2 = &mut x;
x_b2.push_str("World");

// 代码片段5 可写借用不应该有多个
let mut x = "Hello".to_string();
let mut x_b2 = &mut x;
// 下面一行编译报错
// cannot borrow `x` as mutable more than once at a time
let mut x_b3 = &mut x;
x_b2.push_str("World");

4 mut关键字

let声明的变量默认是read-only,类比java中的final。如果要能修改内存中的值,需要借助mut关键字进行声明。

fn test_mut() {
    // 想要通过变量修改指向的内存值,需要mut关键字配合
    let mut p1 = Point { x: 25, y: 25 };
    p1.x = 30;
    println!("{} {}", p1.x, p1.y);
}

5 只读借用传参

上面介绍了赋值场景下,所有权的转移和借用问题。当然,函数调用传参,所有权的转移和借用也是一样的。

fn test_borrow() {
    let p1 = Point { x: 25, y: 25 };
    // 通过&符将原数据的使用权,借用到borrowReadOnly()函数里面
    borrowReadOnly(&p1);
    // p1仍然具有所有权
    println!("{}", p1.x);
}
// p是只读借用,f里面不能修改p.x/p.y
// p只是一个借用,不会延长原数据的生命周期
fn borrowReadOnly(p: &Point) {
	println!("{}", p.x);
}

6 可写借用传参

如果想要在函数里面,修改传参变量所引用的内存,需要借助mut

// 可写借用,借用者可以对内存进行修改
// 借用只是一个使用权,并不具备所有权
fn test_mut_borrow() {
    let mut p = Point { x: 25, y: 25 };
    borrowWritable(&mut p);
    println!("{}", p.x);// 100
}
fn borrowWritable(p: &mut Point) {
    p.x = 100;
}

可写的借用必须3个地方都同时加mut才行:p变量声明、函数参数签名、函数调用传参。

7 总结

  1. Rust语言通过所有权机制的设计,严格限定一块内存在同一个时刻只会被一个变量拥有所有权
  2. 编译器负责跟踪具有所有权的变量的生命周期,并在所有权终结时释放相应的内存。
  3. 由于借用被编译器严格限制在,被借用变量的生命周期内,所以rust中不会出现野指针(比如,访问已经释放的内存)等内存错误,从而保证了内存安全。
  4. 借用也要受约束:可写的借用不能与其他“活跃”借用共存

测试代码位于gitee仓库:https://gitee.com/linysuccess/rust0_100

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值