zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Rust 编程学习笔记Day 5 - Borrow

2023-06-13 09:17:11 时间

昨天(day4) 我们一起学习了Copy语义,在进行变量赋值,传参,函数返回时,如果变量的数据结构实现了Copy trait,就自动使用Copy语义,否则就使用Move语义 转移所有权,后面无法访问该变量。

还有一些数据结构既没有实现Copy trait,也不想转移所有权。这时候就需要用到今天的主角--Borrow语义

Borrow语义

从名字就不难看出,变量的所有权并不会发生转移,就可以被其他上下文借用了。 就像小时借同桌的橡皮, 长大了借房东的房子(要付钱)。 过年回家借女友,来应对亲戚催婚的问题。 很明显借来的,并没有所有权,只拥有临时使用权。 Borrow 语义通过引用语法(& 或者 &mut)来实现。

其实,在 Rust 中,“借用”和“引用”是一个概念,只不过在其他语言中引用的意义和 Rust 不同,所以 Rust 提出了新概念“借用”,便于区分。(你到是便于区分了,对于我们这些新手有点制造概念,增加学习成本了。我按照你的套路理解一大圈,然后你告诉我借用就是引用,就这?)。

默认情况下,Rust的借用都是只读的,就好像借来的房子,退租的时候,里面不能少东西,也不能把人家原来的装修风格变了。当然借来的女友,也不能被写入任何数据。

所以,如果我们想避免 Copy 或者 Move,可以使用借用,或者说引用。

只读借用/只读引用

我们在学习其他语言的时候,函数传参一般方式有:传值,传引用。

但在Rust中没有传引用的概念,Rust所有参数传递都是传值,包括Copy和Move。 在Rust中,必须显式地把某个数据的引用,传给另一个函数。(我理解:传值就是copy栈上的值,不论栈上存的是“指针/地址”还是“值”。)

Rust的引用实现了Copy trait(疑问:只读借用,可变借用都实现了吗?) 所以按照 Copy 语义,这个引用会被复制一份交给要调用的函数。对这个函数来说,它并不拥有数据本身,数据只是临时借给它使用,所有权还在原来的拥有者那里。

在 Rust 里,引用是一等公民,和其他数据类型地位相等。

用之前的错误代码来演示

fn main() {
    let data = vec![1, 2, 3, 4];
    let data1 = data;
    println!("sum of data1: {}", sum(data1));
    println!("data1: {:?}", data1); // error1
    println!("sum of data: {}", sum(data)); // error2
}

fn sum(data: Vec<u32>) -> u32 {
    data.iter().fold(0, |acc, x| acc + x)
}

通过调整成借用的方式,通过编译,并查看值和引用的地址。

fn main() {
    let data = vec![1, 2, 3, 4];
    let data1 = &data;
    // 值的地址是什么?引用的地址又是什么?
    println!(
        "addr of value: {:p}({:p}), addr of data {:p}, data1: {:p}",
        &data, data1, &&data, &data1
    );
    println!("sum of data1: {}", sum(data1));

    // 堆上数据的地址是什么?
    println!(
        "addr of items: [{:p}, {:p}, {:p}, {:p}]",
        &data[0], &data[1], &data[2], &data[3]
    );
}

fn sum(data: &Vec<u32>) -> u32 {
    // 值的地址会改变么?引用的地址会改变么?
    println!("addr of value: {:p}, addr of ref: {:p}", data, &data);
    data.iter().fold(0, |acc, x| acc + x)
}

先别着急run,我们先想一下,data值的地址是否不变,而data1引用的地址,在传给sum()函数后,是否指向同一个地址。 3,2,1.

我们来验证一下,心里的答案 正确与否。

可以看到data1,&data,以及sum()里的data1'都指向data的ptr。这个值是不变的。 但是每个引用的地址都不一样,这是因为上面说到的只读借用实现了Copy trait。 也就意味着引用的赋值(data1 = &data) 引用的传参(sum(data1)) 都会产生新的浅拷贝

现在我们看到虽然有多个只读引用指向了data,但在堆上的真实数据而言,它只属于data一个人,所以值的多个引用,并不影响所有权的唯一性。

那么问题来了!一旦data离开了作用域,被释放了。还有指向data的引用,那就会变成我们想避免的悬挂指针了?类似Golang里的逃逸?

我们明天接着聊!我是老张一个陪你成长的码农。