本内容是对 Rust开发干货集^[1]^ 的实践与扩展.
iter() 不转移所有权
先简单解释下什么叫"转移所有权":
在 Rust 中,"转移所有权"(Ownership Transfer)是一种核心概念,它涉及变量和数据的所有权从一个实体转移到另一个实体。这种机制帮助 Rust 在编译时期管理内存安全,避免悬挂指针和内存泄漏等问题。
例如:
fn main() {
let s1 = String::from("hello");
let s2 = s1; // 所有权从 s1 转移到 s2
// println!("{}", s1); // 这行会引发编译错误,因为 s1 不再拥有数据的所有权. 报错: error[E0382]: borrow of moved value: `s1`
println!("{}", s2); // 正确,s2 现在拥有数据的所有权
}
当 s1 被赋值给 s2 时,s1 的所有权被转移给了 s2。这意味着 s1 不再有效,因此接下来如果使用 s1 将导致编译错误。
iter()
在 Rust 中用于创建集合的迭代器,比如在数组或向量上。iter()
不会转移集合的所有权。相反,它创建一个迭代器,该迭代器借用集合的内容:
fn main() {
let v = vec![1, 2, 3];
for i in v.iter() {
println!("{}", i);
}
// v 仍然有效,因为 iter() 没有取得所有权
println!("Vector: {:?}", v);
}
上例中,v.iter()
创建了一个迭代器,但 v
的所有权没有改变。因此,在迭代之后,仍然可以使用 v
。
这说明iter() 不转移所有权
(因为所有权转移意味着原始变量不再有效)
另外几种创建迭代器的方法: iter_mut()
和 into_iter()
iter_mut()
iter_mut()
方法用于创建一个可变借用(mutable borrow)的迭代器。其允许在迭代过程中修改集合中的元素。
(所有权并没有发生转移)
如下:
fn main() {
let mut v = vec![1, 2, 3];
for i in v.iter_mut() {
*i *= 2; // 将每个元素乘以 2
println!("{}", i);
}
println!("{:?}", v); // 输出: [2, 4, 6]
}
v.iter_mut()
创建了一个可变迭代器,允许修改向量 v 中的每个元素
into_iter()
into_iter()
方法用于创建一个取得所有权(ownership)的迭代器---
这意味着迭代器会消耗(consume)集合,并拥有其元素的所有权。这通常用于在迭代时转移集合中元素的所有权。
如下:
fn main() {
let v = vec![1, 2, 3];
for i in v.into_iter() {
println!("{}", i); // 打印每个元素
}
// println!("{:?}", v); // 这行会编译错误,因为 v 的所有权已经被移动; error[E0382]: borrow of moved value: `v`
}
v.into_iter()
创建了一个获取 v 所有权的迭代器。迭代后,v 不再有效,因为它的所有权已经被迭代器 into_iter()
消耗。
into_iter()
会转移所有权。它创建一个获取集合所有权的迭代器,允许在迭代时转移集合中元素的所有权。一旦使用了 into_iter()
,原始集合将不再有效,因为它的所有权已经被迭代器取得。
即
iter_mut()
用于需要修改集合中元素的场景,但并不转移所有权; 而into_iter()
用于需要转移元素所有权的场景。
iter()
的 cloned()
方法
iter()
方法用于创建一个不可变引用的迭代器,而 cloned()
是这类迭代器的一个方法。cloned()
的作用是将迭代器中的每个元素通过调用其 clone
方法来创建一个新的实例。这通常用于当拥有一个包含引用的迭代器,但需要迭代器中的值的拷贝时。
cloned()
的作用:
- 创建元素的拷贝 :
cloned()
方法适用于迭代器的元素实现了Clone
trait。它会为每个元素创建一个新的实例,这是通过调用每个元素的clone
方法实现的。 - 不转移所有权 :由于
cloned()
仅仅是创建元素的副本,它不会改变原始数据的所有权。
如下例: 假设有一个Vec,其中包含一些数字的引用,现在想要迭代这些数字的拷贝而不是引用本身:
use std::any::type_name;
fn print_type_of<T>(_: &T) {
println!("变量类型为:{}", type_name::<T>());
}
fn main() {
let nums = vec![1, 2, 3];
for num in nums.iter() {
println!("{}", num); // 输出: 1, 2, 3
print_type_of(&num);
}
println!("-----分界线------");
let nums_cloned: Vec<i32> = nums.iter().cloned().collect();
println!("{:?}", nums_cloned); // 输出: [1, 2, 3]
for num in nums_cloned {
println!("{}", num); // 输出: 1, 2, 3
print_type_of(&num);
}
}
输出为:
1
变量类型为:&i32
2
变量类型为:&i32
3
变量类型为:&i32
-----分界线------
[1, 2, 3]
1
变量类型为:i32
2
变量类型为:i32
3
变量类型为:i32
在上例中,使用 cloned()
方法,可以将这些引用转换为实际的数字拷贝,从而创建一个新的 Vec<i32>
。这样,nums_cloned
就包含了 nums
中每个元素的拷贝,而不是引用。
即
cloned()
在 Rust 中用于从迭代器中创建元素的拷贝,特别是当有一个包含引用的迭代器 并希望获得实际值的拷贝时。它是处理引用集合时常用的便捷方法。
iter_mut() 有没有 cloned()方法?
iter_mut()
方法返回的迭代器是一个可变引用的迭代器。由于 cloned()
方法是用于拷贝迭代器中的值,它通常与不可变引用的迭代器(如由 iter()
返回的迭代器)一起使用。cloned()
方法适用于那些实现了 Clone
trait 的元素,它会创建每个元素的拷贝。
对于 iter_mut()
返回的迭代器,由于它提供的是对元素的可变引用(&mut T
),使用 cloned()
方法是不适当的,也不符合 Rust 的安全性原则。可变引用的目的是允许修改集合中的元素,而不是创建它们的拷贝。如果需要修改元素并且需要它们的拷贝,应该首先通过其他方式创建拷贝,然后对这些拷贝进行修改。
因此,在实际的 Rust 编程实践中,iter_mut()
迭代器上不会使用 cloned()
方法。如果需要元素的拷贝,应该使用 iter()
方法来创建一个不可变引用的迭代器,然后在该迭代器上使用 cloned()
map/fold(reduce)/filter的作用
更多可参考 初探函数式编程---以Map/Reduce/Filter为例^[2]^
map用于对迭代器中的 每个元素 应用某个函数/执行某项(会发生修改的)操作,并返回一个新的迭代器。
例如:
let numbers = [1, 2, 3, 4, 5];
let doubles = numbers.iter().map(|x| x * 2);
// doubles将包含[2, 4, 6, 8, 10]
fn main() {
let numbers = [1, 2, 3, 4, 5];
let doubles = numbers.iter().map(|x| x * 2);
for x in doubles {
println!("{}", x);
}
}
输出:
2
4
6
8
10
fold用于将迭代器中的元素进行累积计算,其基本语法是:
iter.fold(init, |acc, x| {
// acc是累积值,x是当前元素
// 返回更新后的acc
})
fold需要两个参数:
- init:初始累积值
- 闭包:接收当前累积值acc和元素x,返回更新后的acc
例如:
fn main() {
let numbers = [1, 2, 3, 4, 5];
let sum = numbers.iter().fold(0, |acc, x| acc + x);
println!("Sum is: {}", sum); // Sum is: 15
}
这里fold的init值为0,闭包中每次将acc和x相加,返回更新后的acc,最终将数组所有元素求和。
另一个例子,连接字符串:
fn main() {
let strings = ["a", "b", "c"];
let concat = strings.iter().fold(String::new(), |acc, s| acc + s);
println!("Concat is: {}", concat); // Concat is: abc
}
fold可以将迭代中的元素进行任意累积计算 ,常见用法包括求和、乘积、字符串连接等。fold()消费器可以实现reduce逻辑
filter用于过滤迭代器中的元素,只保留满足条件的元素。
例如:
let numbers = [1, 2, 3, 4, 5];
let evens = numbers.iter().filter(|&x| x % 2 == 0);
// evens将只包含[2, 4]
fn main() {
let numbers = [1, 2, 3, 4, 5];
let evens: Vec<_> = numbers.iter().filter(|&x| x % 2 == 0).collect();
for x in evens {
println!("{}", x);
}
}
输出:
2
4
filter_map()
可以同时完成转换和过滤
filter_map()
方法结合了过滤(filter
)和映射(map
)功能。
这个方法接收一个闭包,该闭包作用于迭代器的每个元素,并返回 Option<T>
类型。filter_map()
然后自动过滤掉所有返回 None
的元素,并将所有 Some
包裹的值提取出来,形成一个新的迭代器。
- 过滤和转换 :
filter_map()
允许同时对迭代器的元素进行过滤和转换。如果闭包返回Some(value)
,则value
被包含在新迭代器中;如果闭包返回None
,则该元素被过滤掉。 - 可选的转换 :与
map()
相比,filter_map()
允许根据元素的值选择性地包含或排除元素,而不是简单地映射每个元素到另一个值。
举个例子, 假设有一个字符串类型的向量,想将其中的每个字符串转换为整数。但不是所有的字符串都可以转换为整数(例如,某些字符串可能包含非数字字符,如"1ab")。
在这种情况下,就可以使用 filter_map()
来尝试解析每个字符串,并仅保留那些成功解析为整数的元素:
fn main() {
let strings = vec!["3", "seven", "8", "10"];
let numbers: Vec<i32> = strings
.into_iter()
.filter_map(|s| s.parse::<i32>().ok())
.collect();
println!("{:?}", numbers); // 输出: [3, 8, 10]
}
在上例中,filter_map()
尝试将每个字符串转换为 i32
。如果 parse
方法成功(即返回 Ok(value)
),filter_map()
将 Some(value)
包装的 value
加入到新的迭代器中。如果 parse
失败(即返回 Err
),filter_map()
则通过 ok()
方法将 Err
转换为 None
,从而过滤掉这些元素。
通过这种方式,filter_map()
使得同时进行过滤和映射变得简单而高效。
另外一些消费器
上面介绍的 map
、fold
和 filter
,都属于消费器, 消费器在Rust中是指能够消费迭代器的类型
另外还有一些常用的消费器,包括:
- collect():将迭代器收集到集合类型如Vec/String中
collect()消费器可以实现集合类型转化
fn main() {
println!("{:?}", vec![1, 2, 3].iter().collect::<Vec<_>>()); // [1, 2, 3]
}
-
sum():计算迭代器元素的和。
fn main() {
// sum()的返回类型依赖于迭代器的元素类型,这里元素是i32,所以需要明确指定sum_value的类型为i32
let sum_value1: i32 = [1, 2, 3].iter().sum();
println!("{:?}", sum_value1); // 6// 或者使用泛型参数指定 let sum_value2 = [1, 2, 3].iter().sum::<i32>(); println!("{:?}", sum_value2); // 6
}
-
min()/max():找到迭代器最小/最大元素。
fn main() {
let max = [1, 2, 3].iter().max();
println!("{:?}", max.unwrap()); // 3
} -
count():统计迭代器元素数量。
fn main() {
let count = [1, 2, 3, 0, 6].iter().count();
println!("{:?}", count); // 5
} -
any()/all():是否存在(某个元素)/所有元素满足条件。
any()消费器可以查找是否存在满⾜条件的元素,迭代器是惰性的,any消费器可能不需要遍历Iterator
fn main() {
let exists = [1, 2, 3].iter().any(|x| *x == 2);
println!("{:?}", exists); // true, 即判断Vec中是否有值为2的元素
}
fn main() {
let numbers = [2, 4, 6];
let result = numbers.iter().all(|x| x % 2 == 0);
println!("{:?}", result); // true, 即判断Vec中所有元素是否都能被2整除
}
-
find():找到第一个满足条件的元素并输出,否则返回None
fn main() {
let numbers = [1, 2, 3, 4];// 查找numbers中第一个等于3的元素,找到后返回Some(3) let result = numbers.iter().find(|&x| *x == 3); println!("{:?}", result); // Some(3) // 如果没有找到满足条件的元素,则会返回None: let result = numbers.iter().find(|&x| *x == 5); println!("{:?}", result); // None
}
这些消费器都会完全消费迭代器,将其结果返回。它们常用于迭代器计算的最后阶段,将迭代结果转化为具体值。
参考资料
[1]
Rust开发干货集: https://github.com/rustaceanclub/rust-slides/tree/master/20191216-GB-Rust开发干货集
[2]
初探函数式编程---以Map/Reduce/Filter为例: https://juejin.cn/post/7270823313809752076
本文由mdnice多平台发布