难以在 Rust 中创建用于测试目的的模拟 'sink'
Difficulty creating a mock 'sink' for testing purposes in Rust
上下文
我有一个函数可以使用人造丝来并行化一些工作。每个线程都应该将其输出写入一个单独的消费者。实际上,这些消费者通常会写入(单独的)磁盘上的文件。为了测试,我希望它们附加到矢量,然后我可以对其进行断言。
问题
事实上,即使使用此代码的串行版本,我也遇到了问题。
我要测试的函数是func_under_test
。我希望 get_sink
在每次调用时创建并分发一个新的 'sink'(一个 Vec<i32>
),它将在 func_under_test
中。但我也想在我的 main
函数中保留对这些向量的引用,这样我就可以对它们进行断言。我试过这样写:
fn func_under_test<GetSink, F>(
mut get_sink: GetSink
)
where
GetSink: FnMut(usize) -> F,
F: FnMut(i32) -> (),
{
let idxs : Vec<usize> = (0..2).collect();
idxs.iter().for_each(|&i| {
let mut sink = get_sink(i);
sink(0i32);
sink(1i32);
sink(2i32);
});
}
fn main() {
let mut sinks : Vec<&Vec<i32>> = vec![];
let get_sink = |i: usize| {
let mut sink : Vec<i32> = vec![];
sinks.push(&sink);
move |x : i32| sink.push(x)
};
func_under_test(get_sink);
assert_eq!(**sinks.get(0).unwrap(), vec![0i32, 1i32, 2i32]);
}
但是我得到这个编译错误:
error[E0597]: `sink` does not live long enough
--> src/main.rs:23:20
|
20 | let mut sinks : Vec<&Vec<i32>> = vec![];
| --------- lifetime `'1` appears in the type of `sinks`
...
23 | sinks.push(&sink);
| -----------^^^^^-
| | |
| | borrowed value does not live long enough
| argument requires that `sink` is borrowed for `'1`
24 | move |x : i32| sink.push(x)
25 | };
| - `sink` dropped here while still borrowed
error[E0505]: cannot move out of `sink` because it is borrowed
--> src/main.rs:24:9
|
20 | let mut sinks : Vec<&Vec<i32>> = vec![];
| --------- lifetime `'1` appears in the type of `sinks`
...
23 | sinks.push(&sink);
| -----------------
| | |
| | borrow of `sink` occurs here
| argument requires that `sink` is borrowed for `'1`
24 | move |x : i32| sink.push(x)
| ^^^^^^^^^^^^^^ ---- move occurs due to use in closure
| |
| move out of `sink` occurs here
我认为我需要在这种情况下使用类似 Rc
的想法是否正确,因为我需要对每个 Vec<i32>
有两个引用?
由于这些引用中只有一个需要可变,我是否需要在一种情况下使用 Rc<Cell<Vec<i32>>>
,而在另一种情况下只需要使用 Rc<Vec<i32>>
?
您可以使用Arc<Mutex<i32>>
来共享对向量的可变访问。 (Rc
不是线程安全的。)
一个更简洁的解决方案是 typed_arena::Arena
,它允许您基本上编写您尝试过的代码,但角色互换:Vec<i32>
s 始终归竞技场所有(因此它func_under_tests
)。然后,在 all 对竞技场的引用消失后,您可以将其转换为其元素的向量。
use typed_arena::Arena;
fn main() {
let sinks: Arena<Vec<i32>> = Arena::new();
let get_sink = |_i: usize| {
let sink_ref = sinks.alloc(Vec::new());
move |x| sink_ref.push(x)
};
func_under_test(get_sink);
assert_eq!(sinks.into_vec()[0], vec![0i32, 1i32, 2i32]);
}
我想我已经找到了答案。执行测试的线程需要访问内部向量,填充它的线程也是如此。所以它们需要被包裹在一个 Arc
和一个 Mutex
中。但是外部向量没有,因为它只是被 get_sink
闭包借用。
所以 sinks
可以有类型 Vec<Arc<Mutex<Vec<i32>>>
.
无论如何,我可以写这个,它确实编译和运行符合预期:
use rayon::prelude::*;
use std::sync::{Arc, Mutex};
fn func_under_test<G, S>(
get_sink: G)
where
G: Fn(&usize) -> S + Sync,
S: FnMut(i32) -> (),
{
let idxs : Vec<usize> = vec![0, 1, 2];
idxs.par_iter().for_each(|i| {
let mut sink : S = get_sink(i);
for j in 0..3 {
sink(j + 3*(*i) as i32);
}
});
}
fn main() {
let sinks : Vec<Arc<Mutex<Vec<i32>>>> = vec![
Arc::new(Mutex::new(vec![])),
Arc::new(Mutex::new(vec![])),
Arc::new(Mutex::new(vec![])),
];
let get_sink = |i: &usize| {
let m : Arc<Mutex<Vec<i32>>> = sinks.get(*i).unwrap().clone();
move |x: i32| {
m.lock().unwrap().push(x);
}
};
func_under_test(get_sink);
assert_eq!(*sinks.get(0).unwrap().lock().unwrap(), vec![0, 1, 2]);
assert_eq!(*sinks.get(1).unwrap().lock().unwrap(), vec![3, 4, 5]);
assert_eq!(*sinks.get(2).unwrap().lock().unwrap(), vec![6, 7, 8]);
}
似乎 G
在这里实现了正确的特征:它是 Sync
,因为它需要在线程之间共享。但是是不是Send
,因为它不需要在线程之间发送信息。
上下文
我有一个函数可以使用人造丝来并行化一些工作。每个线程都应该将其输出写入一个单独的消费者。实际上,这些消费者通常会写入(单独的)磁盘上的文件。为了测试,我希望它们附加到矢量,然后我可以对其进行断言。
问题
事实上,即使使用此代码的串行版本,我也遇到了问题。
我要测试的函数是func_under_test
。我希望 get_sink
在每次调用时创建并分发一个新的 'sink'(一个 Vec<i32>
),它将在 func_under_test
中。但我也想在我的 main
函数中保留对这些向量的引用,这样我就可以对它们进行断言。我试过这样写:
fn func_under_test<GetSink, F>(
mut get_sink: GetSink
)
where
GetSink: FnMut(usize) -> F,
F: FnMut(i32) -> (),
{
let idxs : Vec<usize> = (0..2).collect();
idxs.iter().for_each(|&i| {
let mut sink = get_sink(i);
sink(0i32);
sink(1i32);
sink(2i32);
});
}
fn main() {
let mut sinks : Vec<&Vec<i32>> = vec![];
let get_sink = |i: usize| {
let mut sink : Vec<i32> = vec![];
sinks.push(&sink);
move |x : i32| sink.push(x)
};
func_under_test(get_sink);
assert_eq!(**sinks.get(0).unwrap(), vec![0i32, 1i32, 2i32]);
}
但是我得到这个编译错误:
error[E0597]: `sink` does not live long enough
--> src/main.rs:23:20
|
20 | let mut sinks : Vec<&Vec<i32>> = vec![];
| --------- lifetime `'1` appears in the type of `sinks`
...
23 | sinks.push(&sink);
| -----------^^^^^-
| | |
| | borrowed value does not live long enough
| argument requires that `sink` is borrowed for `'1`
24 | move |x : i32| sink.push(x)
25 | };
| - `sink` dropped here while still borrowed
error[E0505]: cannot move out of `sink` because it is borrowed
--> src/main.rs:24:9
|
20 | let mut sinks : Vec<&Vec<i32>> = vec![];
| --------- lifetime `'1` appears in the type of `sinks`
...
23 | sinks.push(&sink);
| -----------------
| | |
| | borrow of `sink` occurs here
| argument requires that `sink` is borrowed for `'1`
24 | move |x : i32| sink.push(x)
| ^^^^^^^^^^^^^^ ---- move occurs due to use in closure
| |
| move out of `sink` occurs here
我认为我需要在这种情况下使用类似 Rc
的想法是否正确,因为我需要对每个 Vec<i32>
有两个引用?
由于这些引用中只有一个需要可变,我是否需要在一种情况下使用 Rc<Cell<Vec<i32>>>
,而在另一种情况下只需要使用 Rc<Vec<i32>>
?
您可以使用Arc<Mutex<i32>>
来共享对向量的可变访问。 (Rc
不是线程安全的。)
一个更简洁的解决方案是 typed_arena::Arena
,它允许您基本上编写您尝试过的代码,但角色互换:Vec<i32>
s 始终归竞技场所有(因此它func_under_tests
)。然后,在 all 对竞技场的引用消失后,您可以将其转换为其元素的向量。
use typed_arena::Arena;
fn main() {
let sinks: Arena<Vec<i32>> = Arena::new();
let get_sink = |_i: usize| {
let sink_ref = sinks.alloc(Vec::new());
move |x| sink_ref.push(x)
};
func_under_test(get_sink);
assert_eq!(sinks.into_vec()[0], vec![0i32, 1i32, 2i32]);
}
我想我已经找到了答案。执行测试的线程需要访问内部向量,填充它的线程也是如此。所以它们需要被包裹在一个 Arc
和一个 Mutex
中。但是外部向量没有,因为它只是被 get_sink
闭包借用。
所以 sinks
可以有类型 Vec<Arc<Mutex<Vec<i32>>>
.
无论如何,我可以写这个,它确实编译和运行符合预期:
use rayon::prelude::*;
use std::sync::{Arc, Mutex};
fn func_under_test<G, S>(
get_sink: G)
where
G: Fn(&usize) -> S + Sync,
S: FnMut(i32) -> (),
{
let idxs : Vec<usize> = vec![0, 1, 2];
idxs.par_iter().for_each(|i| {
let mut sink : S = get_sink(i);
for j in 0..3 {
sink(j + 3*(*i) as i32);
}
});
}
fn main() {
let sinks : Vec<Arc<Mutex<Vec<i32>>>> = vec![
Arc::new(Mutex::new(vec![])),
Arc::new(Mutex::new(vec![])),
Arc::new(Mutex::new(vec![])),
];
let get_sink = |i: &usize| {
let m : Arc<Mutex<Vec<i32>>> = sinks.get(*i).unwrap().clone();
move |x: i32| {
m.lock().unwrap().push(x);
}
};
func_under_test(get_sink);
assert_eq!(*sinks.get(0).unwrap().lock().unwrap(), vec![0, 1, 2]);
assert_eq!(*sinks.get(1).unwrap().lock().unwrap(), vec![3, 4, 5]);
assert_eq!(*sinks.get(2).unwrap().lock().unwrap(), vec![6, 7, 8]);
}
似乎 G
在这里实现了正确的特征:它是 Sync
,因为它需要在线程之间共享。但是是不是Send
,因为它不需要在线程之间发送信息。