使用处理动作队列的结构满足借用检查器的要求
Satisfying the borrow checker with a struct that processes a queue of actions
我正在尝试编写一个结构,它在 Vec 中拥有一些数据(或者可能包含可变引用的 Vec - 哪个并不重要),并且可以处理“动作”队列,其中每个动作是某种计算,它改变了这个 Vec 的元素。这是我到目前为止所写内容的一个最小示例:
// some arbitrary data - may be large, so should not be cloned or copied
#[derive(PartialEq)]
struct T(i32, &'static str);
struct S(Vec<T>);
impl S {
fn get_mut(&mut self, t: &T) -> &mut T {
self.0.iter_mut().find(|a| *a == t).unwrap()
}
fn process_actions(&mut self, queue: ActionQueue) {
// some arbitrary calculation on the elements of self.0
for a in queue.actions {
let t1 = self.get_mut(a.t1);
t1.0 += a.t2.0;
}
}
}
#[derive(Debug)]
enum Error {
ActionError,
ActionQueueError,
}
struct Action<'a> {
s: &'a S,
t1: &'a T,
t2: &'a T,
}
impl<'a> Action<'a> {
pub fn new(s: &'a S, t1: &'a T, t2: &'a T) -> Result<Action<'a>, Error> {
if s.0.contains(&t1) && s.0.contains(&t2) {
Ok(Action { s, t1, t2 })
} else {
Err(Error::ActionError)
}
}
}
struct ActionQueue<'a> {
s: &'a S,
actions: Vec<Action<'a>>,
}
impl<'a> ActionQueue<'a> {
pub fn new(s: &'a S, actions: Vec<Action<'a>>) -> Result<ActionQueue<'a>, Error> {
if actions.iter().all(|a| std::ptr::eq(a.s, s)) {
Ok(ActionQueue { s, actions })
} else {
Err(Error::ActionQueueError)
}
}
}
fn main() -> Result<(), Error> {
let t1 = T(1, "a");
let t2 = T(2, "b");
let mut s = S(vec![t1, t2]);
let a = Action::new(&s, &t1, &t2)?; // error: borrow of moved value: `t1`
let q = ActionQueue::new(&s, vec![a])?;
s.process_actions(q); // cannot borrow `s` as mutable because it is also borrowed as immutable
Ok(())
}
这有一些问题:
- 我无法创建操作,因为 t1 和 t2 已经被移动。
- 即使我可以,我也无法处理动作队列,因为 s 已经在动作中被不变地借用了。我希望 Action(和 ActionQueue)包含对 s 的引用的原因是,据我所知,使用类型可以防止创建无效数据,例如引用未包含在 s 中的数据的操作(由 s 处理)。
- S 的
get_mut
功能看起来有点怪怪的,好像我不应该有这样的功能。
我明白错误发生的原因及其含义,但我看不出有什么方法可以解决这个问题,因为为了定义任何 Action,我需要参考 s.0
的元素我不允许这样做。所以我的问题是,应该如何重写这段代码才能真正编译?设计是否完全不同并不重要,只要它达到相同的目标(即允许排队等待稍后处理的动作)。
我通过以下方式获得了所需的行为:
- 我将
T
中的引用更改为 usize
索引,它们引用与以前相同的数据,但现在仅在执行操作时引用,因此在新的范围内引用。这确实需要稍微多一些考虑,如果您将 T
项目移动到位(在 S
向量中),将会更加昂贵,因为您需要找到项目的新索引 -可能会阻止它(我没有测试过,但不明白为什么除了简单的迭代搜索之外还需要什么)
2.limiting借用的次数。我删除了 Action
中的 S
引用,这使得这成为可能,尽管它牺牲了一些可靠性(稍后会详细介绍)它无论如何都是多余的。
usize
索引搜索解决了get_mut()
的问题,但也意味着现在要使用数据,您必须引用T
的各个字段。 get_mut()
之所以看起来如此 hacky,是因为它本质上是一种超压缩迭代搜索。如果您将 T
移到 S
中,那将非常有帮助,因此您可能希望将其保留在您的源代码中,但在本示例中不需要它。
// some arbitrary data - may be large, so should not be cloned or copied
use std::marker::PhantomData;
struct T(i32, &'static str);
struct S(Vec<T>);
impl S {
fn process_actions(&mut self, changes: [usize;2]) {
let mut a = self.0[changes[0]].0;
a += self.0[changes[1]].0;
println!("the total is {}",a);
}
}
#[derive(Debug)]
enum Error {
ActionQueueError,
}
struct Action<'a> {
t1: usize,
t2: usize,
phantom_lifetime: PhantomData<&'a u8>,
}
impl<'a> Action<'a> {
pub fn new(ts: [usize;2]) -> Action<'a> {
Action {
t1: ts[0],
t2: ts[1],
phantom_lifetime: PhantomData,
}
}
}
struct ActionQueue<'a> {
s: S,
actions: Vec<Action<'a>>,
}
impl<'a> ActionQueue<'a> {
pub fn new(s: S, actions: Vec<Action<'a>>) -> Result<ActionQueue<'a>, Error> {
if actions.iter().all(|a| s.0.len() > a.t1 && s.0.len() > a.t2) {
Ok(ActionQueue { s, actions })
} else {
Err(Error::ActionQueueError)
}
}
pub fn process_many_actions(&mut self) {
for gos in self.actions.iter_mut() {
self.s.process_actions([gos.t1,gos.t2])
}
}
}
fn main() -> Result<(), Error> {
let t1 = T(1, "a");
let t2 = T(2, "b");
let s = S(vec![t1, t2]);
let a = Action::new([0,1]);
let mut q = ActionQueue::new(s, vec![a])?;
q.process_many_actions();
Ok(())
}
出于安全考虑,您可能注意到我从问题中删除了两项检查。第一个检查是一个相当简单的替换,唯一真正的变化是现在它在移动到 ActionQueue
时进行检查
立即,它检查索引是否会找到 something,而不是它 110% 是该索引中与您放入时相同的元素。第二次检查已过时,因为S
被移动到 ActionQueue
然后改变了一个方法
至 ActionQueue
。虽然调试起来可能有点困难,但这至少会使函数调用更简单、更安全。
你把自己搞得一团糟!将来,为了让调试更容易(或者至少让回答者花费更少的时间,并吸引更多的人......这个网站上还有其他人,我认为《?》),记住 rust 借用的规则 如你所写。这不会消除错误,甚至不会消除错误,但应该让您远离曾经遇到的大麻烦。
我正在尝试编写一个结构,它在 Vec 中拥有一些数据(或者可能包含可变引用的 Vec - 哪个并不重要),并且可以处理“动作”队列,其中每个动作是某种计算,它改变了这个 Vec 的元素。这是我到目前为止所写内容的一个最小示例:
// some arbitrary data - may be large, so should not be cloned or copied
#[derive(PartialEq)]
struct T(i32, &'static str);
struct S(Vec<T>);
impl S {
fn get_mut(&mut self, t: &T) -> &mut T {
self.0.iter_mut().find(|a| *a == t).unwrap()
}
fn process_actions(&mut self, queue: ActionQueue) {
// some arbitrary calculation on the elements of self.0
for a in queue.actions {
let t1 = self.get_mut(a.t1);
t1.0 += a.t2.0;
}
}
}
#[derive(Debug)]
enum Error {
ActionError,
ActionQueueError,
}
struct Action<'a> {
s: &'a S,
t1: &'a T,
t2: &'a T,
}
impl<'a> Action<'a> {
pub fn new(s: &'a S, t1: &'a T, t2: &'a T) -> Result<Action<'a>, Error> {
if s.0.contains(&t1) && s.0.contains(&t2) {
Ok(Action { s, t1, t2 })
} else {
Err(Error::ActionError)
}
}
}
struct ActionQueue<'a> {
s: &'a S,
actions: Vec<Action<'a>>,
}
impl<'a> ActionQueue<'a> {
pub fn new(s: &'a S, actions: Vec<Action<'a>>) -> Result<ActionQueue<'a>, Error> {
if actions.iter().all(|a| std::ptr::eq(a.s, s)) {
Ok(ActionQueue { s, actions })
} else {
Err(Error::ActionQueueError)
}
}
}
fn main() -> Result<(), Error> {
let t1 = T(1, "a");
let t2 = T(2, "b");
let mut s = S(vec![t1, t2]);
let a = Action::new(&s, &t1, &t2)?; // error: borrow of moved value: `t1`
let q = ActionQueue::new(&s, vec![a])?;
s.process_actions(q); // cannot borrow `s` as mutable because it is also borrowed as immutable
Ok(())
}
这有一些问题:
- 我无法创建操作,因为 t1 和 t2 已经被移动。
- 即使我可以,我也无法处理动作队列,因为 s 已经在动作中被不变地借用了。我希望 Action(和 ActionQueue)包含对 s 的引用的原因是,据我所知,使用类型可以防止创建无效数据,例如引用未包含在 s 中的数据的操作(由 s 处理)。
- S 的
get_mut
功能看起来有点怪怪的,好像我不应该有这样的功能。
我明白错误发生的原因及其含义,但我看不出有什么方法可以解决这个问题,因为为了定义任何 Action,我需要参考 s.0
的元素我不允许这样做。所以我的问题是,应该如何重写这段代码才能真正编译?设计是否完全不同并不重要,只要它达到相同的目标(即允许排队等待稍后处理的动作)。
我通过以下方式获得了所需的行为:
- 我将
T
中的引用更改为usize
索引,它们引用与以前相同的数据,但现在仅在执行操作时引用,因此在新的范围内引用。这确实需要稍微多一些考虑,如果您将T
项目移动到位(在S
向量中),将会更加昂贵,因为您需要找到项目的新索引 -可能会阻止它(我没有测试过,但不明白为什么除了简单的迭代搜索之外还需要什么)
2.limiting借用的次数。我删除了 Action
中的 S
引用,这使得这成为可能,尽管它牺牲了一些可靠性(稍后会详细介绍)它无论如何都是多余的。
usize
索引搜索解决了get_mut()
的问题,但也意味着现在要使用数据,您必须引用T
的各个字段。get_mut()
之所以看起来如此 hacky,是因为它本质上是一种超压缩迭代搜索。如果您将T
移到S
中,那将非常有帮助,因此您可能希望将其保留在您的源代码中,但在本示例中不需要它。
// some arbitrary data - may be large, so should not be cloned or copied
use std::marker::PhantomData;
struct T(i32, &'static str);
struct S(Vec<T>);
impl S {
fn process_actions(&mut self, changes: [usize;2]) {
let mut a = self.0[changes[0]].0;
a += self.0[changes[1]].0;
println!("the total is {}",a);
}
}
#[derive(Debug)]
enum Error {
ActionQueueError,
}
struct Action<'a> {
t1: usize,
t2: usize,
phantom_lifetime: PhantomData<&'a u8>,
}
impl<'a> Action<'a> {
pub fn new(ts: [usize;2]) -> Action<'a> {
Action {
t1: ts[0],
t2: ts[1],
phantom_lifetime: PhantomData,
}
}
}
struct ActionQueue<'a> {
s: S,
actions: Vec<Action<'a>>,
}
impl<'a> ActionQueue<'a> {
pub fn new(s: S, actions: Vec<Action<'a>>) -> Result<ActionQueue<'a>, Error> {
if actions.iter().all(|a| s.0.len() > a.t1 && s.0.len() > a.t2) {
Ok(ActionQueue { s, actions })
} else {
Err(Error::ActionQueueError)
}
}
pub fn process_many_actions(&mut self) {
for gos in self.actions.iter_mut() {
self.s.process_actions([gos.t1,gos.t2])
}
}
}
fn main() -> Result<(), Error> {
let t1 = T(1, "a");
let t2 = T(2, "b");
let s = S(vec![t1, t2]);
let a = Action::new([0,1]);
let mut q = ActionQueue::new(s, vec![a])?;
q.process_many_actions();
Ok(())
}
出于安全考虑,您可能注意到我从问题中删除了两项检查。第一个检查是一个相当简单的替换,唯一真正的变化是现在它在移动到 ActionQueue
时进行检查
立即,它检查索引是否会找到 something,而不是它 110% 是该索引中与您放入时相同的元素。第二次检查已过时,因为S
被移动到 ActionQueue
然后改变了一个方法
至 ActionQueue
。虽然调试起来可能有点困难,但这至少会使函数调用更简单、更安全。
你把自己搞得一团糟!将来,为了让调试更容易(或者至少让回答者花费更少的时间,并吸引更多的人......这个网站上还有其他人,我认为《?》),记住 rust 借用的规则 如你所写。这不会消除错误,甚至不会消除错误,但应该让您远离曾经遇到的大麻烦。