最近,在使用Rust
时遇到了Reborrow
的概念,记录下来以备以后参考。
1. 起因
起因准备对数据进行Min-Max
标准化处理,也就是将一系列数据映射到一个新的范围。
首先,需要遍历数据,找出其中的最大值和最小值,然后通过公式改变原始数据集的值。
Min-Max
公式:标准化后的值 = (原始值 - 最小值) / (最大值 - 最小值)
简化后的代码如下:
fn main() {
let mut values = vec![10.5, 22.3, 103.5, 45.75];
let v = &mut values;
println!("原始数据: {:#?}", v);
let mut max = f64::MIN;
let mut min = f64::MAX;
for n in v {
if *n > max {
max = *n;
}
if *n < min {
min = *n;
}
}
println!("max is {}", max);
println!("min is {}", min);
println!("开始Min-Max标准化处理...");
for n in v {
*n = (*n - min) / (max - min);
}
println!("处理后数据: {:#?}", values);
}
运行时有如下错误:
error[E0382]: use of moved value: `v`
--> src/main.rs:22:14
|
3 | let v = &mut values;
| - move occurs because `v` has type `&mut Vec<f64>`, which does not implement the `Copy` trai
t
...
9 | for n in v {
| - `v` moved due to this implicit call to `.into_iter()`
...
22 | for n in v {
| ^ value used here after move
|
大概是第9行遍历v
的找出最大值和最小值时候,可变借用v的使用权已经转移了,
所以在第22行想再次遍历v去修改值的时候,出现错误。
这里,因为Vector
没有实现Copy Trait
,所以它的可变借用在第一次遍历时,由于隐式的调用了.into_iter()
,所有权发生了转移。
如果想多次遍历Vector
,可以使用它的不可变借用,比如定义let v = &values;
那么,就可以多次遍历v
,因为不可变借用都实现了Copy Trait
。
但是,我第二次遍历v
的时候,还需要修改其中的值,所以必须定义为可变借用let v = &mut values;
通过查询资料,发现Reborrow
的机制可以实现上面的需求。
2. Reborrow概念
借用(Borrow
)是Rust
中的一个重要概念,它是允许代码访问某个值而不获取其所有权的一种机制。
而Reborrow
则是指在一个已存在的借用基础上创建一个新的借用,
这个新的借用可以是不可变的,也可以是可变的(前提是原始借用是可变的,并且没有其他借用存在)。
总的来说,Reborrow
通过在已存在的借用上创建新的借用,从而扩展引用的生命周期并在更广泛的作用域内安全地访问值。
3. 解决方法
下面通过实践来检验对Reborrow
概念的理解。
回到第一节中遇到的问题,解决方式就是在第一次遍历v
时(第9行),不要把所有权转移出去,
这样,第二次遍历v
(第22行)的时候,就不会报出"value used here after move"
的错误。
根据Reborrow
的机制,我们在第9行可以Reborrow
可变借用v
,这样转移出去的是被再次借用的v
,而不是v
本身。
改变方法很简单,第9行改为for n in &*v {
即可,也就是先还原v(*v
),然后Reborrow
(&*v
)。
修改后再次运行代码:
$ cargo run
原始数据: [
10.5,
22.3,
103.5,
45.75,
]
max is 103.5
min is 10.5
开始Min-Max标准化处理...
处理后数据: [
0.0,
0.12688172043010754,
1.0,
0.3790322580645161,
]
values
中的数据可以正常转换了。
注意,这里是将v
Reborrow成一个不可变借用&*v
,因为我第一次遍历时不需要改变v
。
如果想v
Reborrow成一个可变借用,可以写成:&mut *v
。