【问题标题】:How to store a reference in a Vec and use this later on in Rust?如何在 Vec 中存储引用并稍后在 Rust 中使用它?
【发布时间】:2020-12-03 19:59:48
【问题描述】:

我需要创建一个 Vec 来跟踪我正在创建的对象并更改其状态以供以后使用。但是如果我在获取存储在 vec 上的对象时使用克隆,它的状态不会更新我该怎么做?

#[derive(Debug, Clone)]
struct Movement {
  x: i32,
  y: i32,
}

fn main() {
   let mut current = Some(Movement { x: 1, y: 2 });

   let mut stack = Vec::new();

   stack.push(&current);

   current.as_mut().unwrap().x = 2;

   println!("m: {:?}", current);
   println!("stack.m: {:?}", stack.pop());
   current = None;
}

【问题讨论】:

  • 视情况而定,但很可能您需要 RC 和 RefCell 的某种组合,但您可能需要考虑调整您的设计。 Rust 不会让共享可变状态变得容易。
  • 有什么原因不能直接将结构存储在get中?
  • 我正在捕获鼠标事件并将位置存储在这个结构中,代码循环运行并切换当前变量,在某些时候我迭代 vec 访问值,代码更复杂不是这个,而是这个表达的想法。
  • 也许只是将索引存储到您的向量中?
  • 将所有鼠标事件存储在vector中并跟踪当前事件的索引可能会更容易。

标签: rust


【解决方案1】:

你试图改变某些东西,同时在其他地方持有对它的共享引用。这是一个问题,因为 Rust 假设共享 (&) 引用后面的内容不会发生变异,以证明您的代码在多线程上下文和存在某些优化的情况下是正确的。

要修复它,您可以告诉编译器忘记多线程并跳过这些优化。一种方法是使用RefCell

use std::cell::RefCell;

#[derive(Debug, Clone)]
struct Movement {
    x: i32,
    y: i32,
}

fn main() {
    let mut current = Some(RefCell::new(Movement { x: 1, y: 2 }));
    //                     ^^^^^^^^^^^^ put it in a `RefCell`
    let mut stack = Vec::new();

    stack.push(&current);

    // note: as_ref, not as_mut
    current.as_ref().unwrap().borrow_mut().x = 2;

    println!("m: {:?}", current);
    println!("stack.m: {:?}", stack.pop());
    current = None;
}

如果您需要在不同线程之间共享对象,您可能需要MutexRwLock。如果要共享的东西是可以简单复制的,比如单个i32,您可以使用Cell<i32>(非线程安全)或AtomicI32(线程安全)。这些统称为内部可变性类型,因为它们允许通过共享引用来改变内部值。

但内部可变性只能让你走这么远。将局部变量的引用放在Vec 中不是很灵活,因为Vec 不能比它引用的任何变量都活得更久。一个常见的习惯是将对象移动到Vec 并使用索引而不是内置引用来访问它们。这是一种方法:

#[derive(Debug, Clone)]
struct Movement {
    x: i32,
    y: i32,
}

fn main() {
    // this object will be moved into the vector
    let temp = Some(Movement { x: 1, y: 2 });

    let mut stack = Vec::new();

    // now we set `current` to be the index of the object in the vector, and use
    // `stack[current_index]` to refer to the object itself
    let current_index = stack.len();
    stack.push(temp);
    
    // you could also do something like this to get a mutable reference, but it
    // would prevent you from doing anything else with `stack` until you are done with
    // `current` (`stack[current_index]` does not have this limitation):
    //let current = stack.last_mut().unwrap()

    stack[current_index].as_mut().unwrap().x = 2;

    println!("m: {:?}", stack[current_index]);
    println!("stack.m: {:?}", stack.pop());
}

还有更多选择。例如,您可以将RefCell 放在Rc(引用计数智能指针)中并克隆它以在Vec 和局部变量之间共享所有权。在没有关于您的目标的更多信息的情况下,我倾向于对Vec 进行索引,以避免共享突变的复杂性,但设计最终是您的选择。

另见

【讨论】:

  • 会阻止你对 stack 做任何其他事情 据我所知,它的行为应该与 index_mut 大致相同,如果我错了,你能解释一下吗? play.rust-lang.org/…
  • @ÖmerErden 你是对的,你可以再次使用stack,但直到你用完current。所以像this 这样的东西不起作用。只存储一个索引没有这个问题,因为索引不会借用Vec,所以每次访问stack[current_index] 时它都会使用“新”借用。 (我将问题编辑得更明确。)
  • 现在更清楚了,感谢您的解释。我假设 OP 拥有对象的所有权并使用 Stack(DS) 来找出下一个对象的引用,而不是堆栈的所有元素,但这可能仍需要澄清。如果 OP 总是可以使用 last_mut() 访问当前元素,但是如果 OP 想要访问堆栈中的任意元素,那么 last_mut() 将无用。
  • 它与索引解决方案一起使用,有点复杂,因为我必须重构一点,但我避免了一些借用检查器问题。
【解决方案2】:

要获得对 vec 中值的可变引用,您可以执行以下操作:

let idx = stack.len() - 1;
if let Some(pos) = stack.get_mut(idx) {
  pos.x = 2
} else {
  ..
}

这专门获取您放入vecstack.push(current) 的最后一个对象。请注意,然后我会将对象完全移动到vec 中,而不仅仅是对它们的引用,并使用上述方法访问它们。 这是您的示例对此进行了更正:

#[derive(Debug, Clone)]
struct Movement {
  x: i32,
  y: i32,
}

fn main() {
  let mut stack = Vec::new();
  stack.push(Movement { x: 1, y: 2 });

  let idx = stack.len()-1;
  let mut current = stack.get_mut(idx).unwrap();
  current.x = 2;

  println!("m: {:?}", current);
  println!("stack.m: {:?}", stack.pop());
}

【讨论】:

  • 解释是对的,但是代码很不对。如果您要在答案中发布代码,请确保它按照您所说的去做。
  • 使用last()last_mut() 可能是强调堆栈行为(peek)的更好选择。
  • @ÖmerErden 是的,你是对的,但我选择关注 get_mut,因为 OP 说他可能也在访问其他元素,所以想使用一种也可以用于此的方法跨度>
猜你喜欢
  • 1970-01-01
  • 2015-02-12
  • 1970-01-01
  • 1970-01-01
  • 2019-06-13
  • 2014-12-11
  • 2021-12-02
  • 2018-08-08
  • 1970-01-01
相关资源
最近更新 更多