我正在try 编写一个 struct ,它在一个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(())
}

这有几个问题:

  1. 我无法创建动作,因为t1和t2已被移动.
  2. 即使可以,我也无法处理操作队列,因为s已经在操作中被永久borrow .我希望Action(和ActionQueue)包含对s的引用的原因是,据我所知,最好使用类型来防止创建无效数据,例如引用s中未包含的数据的Action(由s处理).
  3. S的get_mut个函数似乎有点奇怪,有点粗糙,好像我不需要这样的函数.

我理解错误发生的原因以及它们的含义,但我不认为有任何方法可以绕过这个问题,因为为了定义任何操作,我需要参考s.0的元素,而我不允许这样做.所以我的问题是,这段代码应该如何重写,以便它能够真正编译?设计是否完全不同并不重要,只要它实现了相同的目标(即允许将稍后处理的操作排队).

推荐答案

我通过以下方式获得了想要的行为:

  1. 我将T个索引中的引用更改为usize个索引,这些索引引用的数据与以前相同,但现在只有在执行某个操作时才引用,因此在一个新的范围内.这确实需要更多的先见之明,而且如果你将T个项目移到适当的位置(在S向量中),成本会高得多,因为你需要找到项目的新索引——可能会阻止它(我还没有测试过,但不明白为什么只需要简单的迭代搜索)

2.限制借款数量.我在Action中删除了S引用,这使得这成为可能,尽管它牺牲了一些可靠性(稍后会详细介绍),但无论如何它都是多余的.

  1. usize索引搜索解决了get_mut()问题,但也意味着现在要使用数据,必须参考T的各个字段.get_mut()之所以看起来如此黑客,是因为它本质上是一个超压缩的迭代搜索.如果将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 时进行判断

这正是你让自己陷入的困境!将来,为了让调试变得更容易(或者至少减少回答者的时间,吸引更多的人……我认为这个网站上还有其他人),记住借 rust 的规则.这不会消除错误,即使是很近的错误,但应该可以避免你遇到的大麻烦.

Rust相关问答推荐

将内部类型作为参数的泛型 struct 上的方法

为什么类型需要在这个代码中手动指定,在rust?

为什么复印是豆荚的一个重要特征?

在Rust中,在实现特征`Display`时,如何获取调用方指定的格式?

在Rust中有没有办法在没有UB的情况下在指针和U64之间进行转换?

交换引用时的生命周期

铁 rust 中的泛型:不能将`<;T作为添加>;::Output`除以`{Float}`

在Rust中,Box:ed struct 与普通 struct 在删除顺序上有区别吗?

完全匹配包含大小写的整数范围(&Q;)

UnsafeCell:它如何通知 rustc Select 退出基于别名的优化?

我可以在 Rust 中 serde struct camel_case 和 deserde PascalCase

如何将 C++ 程序链接到 Rust 程序,然后将该 Rust 程序链接回 C++ 程序? (cpp -> rust -> cpp)

Rust:`sort_by` 多个条件,冗长的模式匹配

&str 的编译时拆分是否可能?

SDL2 没有在终端键上触发?

为什么 no_std crate 可以依赖于使用 std 的 crate?

Rust,我如何正确释放堆分配的内存?

意外的正则表达式模式匹配

具有生命周期和以后引用的可变方法

为什么这里需要类型注解?