我对Rust还很陌生(试图离开果朗(。所以我在Go中写了一段代码,并决定尝试在Rust中写。
实际代码如下:
use rand::Rng;
use std::vec::Vec;
use std::thread::{self, JoinHandle};
pub struct WorkManager {
pub threshold: i32,
}
impl WorkManager {
pub fn new(trs: i32) -> Self {
Self { threshold: trs }
}
pub fn run_job<T: Send + Sync>(&self, input: &'static Vec<T>, f: fn(&T) -> T) -> Vec<T> {
if input.len() > self.threshold as usize {
let mut guards: Vec<JoinHandle<Vec<T>>> = vec!();
for chunk in input.chunks(self.threshold as usize) {
let chunk = chunk.to_owned();
let g = thread::spawn(move || chunk.iter().map(|x| f(x)).collect());
guards.push(g);
};
let mut result: Vec<T> = Vec::with_capacity(input.len());
for g in guards {
result.extend(g.join().unwrap().into_iter());
}
result
} else {
return input.iter().map(|x| f(x)).collect::<Vec<T>>();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const TRS_TEST: i32 = 10;
fn testing_function(a: &u32) -> u32 {
return 3 * a;
}
#[test]
fn test_single() {
let wm: WorkManager = WorkManager::new(TRS_TEST);
let values: Vec<u32> = rand::thread_rng()
.sample_iter(&Uniform::from(0..20))
.take(TRS_TEST as usize - 1)
.collect();
assert_eq!(
wm.run_job(&values, testing_function),
values
.iter()
.map(|x| testing_function(x))
.collect::<Vec<u32>>()
);
}
}
所以问题是当我运行货物测试时:
error[E0597]: `values` does not live long enough
--> src/lib.rs:50:24
|
50 | wm.run_job(&values, testing_function),
| -----------^^^^^^^-------------------
| | |
| | borrowed value does not live long enough
| argument requires that `values` is borrowed for `'static`
...
56 | }
| - `values` dropped here while still borrowed
首先,我在函数声明中没有使用"static",但我导致了我读了很多关于生命周期的书,但我似乎错过了一些东西,有什么想法吗?
看起来您想要以下行:
let chunk = chunk.to_owned();
从chunk
的&[T]
类型创建Vec<T>
。但这并没有奏效。这里发生的事情很奇怪。
你想点击以下impl:
impl<T: Clone> ToOwned for [T]
让我们假设我们是编译器。我们为类型&[T]
寻找一个名为to_owned()
的方法。这个过程是如何运作的?你可以在What are Rust';精确的自动取消引用规则?,但在这种情况下会发生以下情况:
- 由于我们已经通过了
&[T]
,我们首先对采用&[T]
的方法进行循环。有这样一个——<[T] as ToOwned>::to_owned()
取&[T]
,因为ToOwned::to_owned()
取&self
,而self
是[T]
。这个方法实施了吗?嗯,是的,如果T: Clone
。这样行吗?不,我们没有这个界限。继续前进 - 寻找一种采用
&&[T]
的方法。它存在吗?是的,<&[T] as ToOwned>::to_owned()
。是否已实施?有一个覆盖impl<T: Clone> ToOwned for T
,所以如果&[T]: Clone
成立,它就会被实现。是吗?是的:每个共享引用都是Copy
,每个Copy
也是Clone
(Copy
将Clone
作为超性状(
所以我们找到了<&&[T] as ToOwned>::to_owned()
。这个方法返回什么?它返回&[T]
(因为<T: Clone as ToOwned>::Owned == T
,在我们的例子中,T
是&[T]
(。总之,我们最终没有创建Vec<T>
,而是创建了&[T]
——或者只是复制了我们的参考文献,根本没有解决问题!
如果我们使用更明确的to_vec()
,编译器的错误也会更有帮助:
error[E0277]: the trait bound `T: Clone` is not satisfied
--> src/lib.rs:31:35
|
31 | let chunk = chunk.to_vec();
| ^^^^^^ the trait `Clone` is not implemented for `T`
|
note: required by a bound in `slice::<impl [T]>::to_vec`
help: consider further restricting this bound
|
27 | pub fn run_job<T: Send + Sync + std::clone::Clone>(&self, input: &Vec<T>, f: fn(&T) -> T) -> Vec<T> {
| +++++++++++++++++++
应用这个建议并没有结束这个故事,尽管我们提出了:
error[E0310]: the parameter type `T` may not live long enough
--> src/lib.rs:32:25
|
27 | pub fn run_job<T: Send + Sync + Clone>(&self, input: &Vec<T>, f: fn(&T) -> T) -> Vec<T> {
| -- help: consider adding an explicit lifetime bound...: `T: 'static +`
...
32 | let g = thread::spawn(move || chunk.iter().map(|x| f(x)).collect());
| ^^^^^^^^^^^^^ ...so that the type `[closure@src/lib.rs:32:39: 32:83]` will meet its required lifetime bounds...
|
note: ...that is required by this bound
error[E0310]: the parameter type `T` may not live long enough
--> src/lib.rs:32:25
|
27 | pub fn run_job<T: Send + Sync + Clone>(&self, input: &Vec<T>, f: fn(&T) -> T) -> Vec<T> {
| -- help: consider adding an explicit lifetime bound...: `T: 'static +`
...
32 | let g = thread::spawn(move || chunk.iter().map(|x| f(x)).collect());
| ^^^^^^^^^^^^^ ...so that the type `Vec<T>` will meet its required lifetime bounds...
|
note: ...that is required by this bound
发生这种情况是因为T
可以间接地包含一个生存期,例如它可以是&'something_non_static i32
。添加绑定的T: 'static + ...
确实修复了所有错误,现在它可以工作了!
这是我们能做的最好的事情吗?绝对没有。我们正在复制我们的数据,我们并不真的需要它——我们这样做只是为了满足借款检查器的要求!
由于我们并不真正需要我们的数据是'static
,而且我们只是因为使用线程而被迫使用它,所以我们最好的选择是使用交叉梁的作用域线程(它们也将是std的一部分(:
pub fn run_job<T: Send + Sync>(&self, input: &[T], f: impl Fn(&T) -> T + Sync) -> Vec<T> {
if input.len() > self.threshold as usize {
crossbeam::scope(|scope| {
let mut guards = vec![];
for chunk in input.chunks(self.threshold as usize) {
let g = scope.spawn(|_| chunk.iter().map(|x| f(x)).collect::<Vec<_>>());
guards.push(g);
}
guards.into_iter().flat_map(|g| g.join().unwrap()).collect()
})
.unwrap()
} else {
input.iter().map(|x| f(x)).collect()
}
}
注意,我还修复了一些不规则的部分:例如,传递&Vec
是一个反模式——而是传递一个切片。还有更少的类型注释,更多的迭代器,impl Fn
而不是fn
,等等
作用域线程允许我们共享非'static
数据,代价是我们的线程必须在返回之前完成(在删除作用域对象之前,它将隐式地join()
(。既然我们无论如何都这么做了,那也没关系。
但我们只是在重新发明轮子。有一个库可以自动将工作拆分为线程,并且使用高级的工作窃取技术(人造丝!使用它很简单:
pub fn run_job<T: Send + Sync>(&self, input: &[T], f: impl Fn(&T) -> T + Send + Sync) -> Vec<T> {
use rayon::prelude::*;
input.par_iter().map(f).collect()
}
是的,仅此而已(请注意,这并不能保证订单(!
因此,在您试图调用的函数中,指针/引用被标记为静态生存期。这意味着,对于整个程序,该指针必须是活动的。所以,我真的不知道如何在这里完全修复它,因为.clouds方法需要静态生存期:(对不起