通用类型、所有权和持久化数据结构

5

TL;DR 我如何在泛型类型上构建数据结构,这些数据结构引用共享的基础数据?

这个问题涉及到Rust中语义和良好的数据建模。下面的代码是我问题的(更)简化版本,以突出我的具体问题,而不是我的实际代码。

目标是创建一个函数,该函数构建多个向量,这些向量包含对泛型类型的共享数据的引用。在以下示例的术语中,我希望能够返回一个集合,该集合可以存储Struct1Struct2(由特征Trait抽象),但由于(在我的真实代码中)Struct1Struct2相对较大,并且将被相对频繁地存储在相对许多位置,因此我宁愿存储对共享数据的引用,而不是将它们全部复制到各个位置。

我目前面临的问题(并且有许多中间修订)是:

  1. 由于我尝试存储的数据类型是Trait,而不是Sized,因此我需要在我的向量中存储引用
  2. 由于每个结构将从多个向量引用,因此需要一个&引用
  3. 在哲学上,没有给定结构的“所有者”,因此没有“正确”的地方可以将我的结构放置在其中,以便它们不会随着build_vectors函数超出范围。
    1. 我考虑通过Traits的全局向量来解决这个问题,其中我可以指向引用,但不幸的是,上述问题(1)似乎排除了该策略。

.

struct Struct1;
struct Struct2;

trait Trait { fn name(&self) -> &str; }
impl Trait for Struct1 { fn name(&self) -> &str { "Struct1" } }
impl Trait for Struct2 { fn name(&self) -> &str { "Struct2" } }

fn shallow_copy<'a>(v: &'a Vec<&'a Box<Trait>>) -> Vec<&'a Box<Trait>> {
   v.iter().map(|x|*x).collect()
}

fn build_vectors<'a>() -> (Vec<&'a Box<Trait>>, Vec<&'a Box<Trait>>) {
  let box_struct1: &Box<Trait> = &(Box::new(Struct1) as Box<Trait>);
  let box_struct2: &Box<Trait> = &(Box::new(Struct2) as Box<Trait>);

  let vec1: Vec<&Box<Trait>> = vec![box_struct1];
  let mut vec2: Vec<&Box<Trait>> = shallow_copy(&vec1);

  vec2.push(box_struct2);

  (vec1, vec2)
}

fn join_names(v: &Vec<&Box<Trait>>) -> String {
   v.iter().map(|s| s.name()).collect::<Vec<_>>().connect(" ")
}

fn main() {
  let (vec1, vec2) = build_vectors();

  println!("vec1: {}", join_names(&vec1));
  println!("vec2: {}", join_names(&vec2));
}

期望的输出是:
vec1: Struct1
vec2: Struct1 Struct2
1个回答

7
这似乎是使用 Rc 的完美案例。 Rc 是一种引用计数类型,允许您拥有同一值的多个所有者。
use std::rc::Rc;

struct Struct1;
struct Struct2;

trait Trait { fn name(&self) -> &str; }
impl Trait for Struct1 { fn name(&self) -> &str { "Struct1" } }
impl Trait for Struct2 { fn name(&self) -> &str { "Struct2" } }

fn shallow_copy<'a>(v: &[Rc<Trait + 'a>]) -> Vec<Rc<Trait + 'a>> {
   v.iter().map(|x| x.clone()).collect()
}

fn build_vectors() -> (Vec<Rc<Trait>>, Vec<Rc<Trait>>) {
  let vec1: Vec<Rc<Trait>> = vec![Rc::new(Struct1)];
  let mut vec2: Vec<Rc<Trait>> = shallow_copy(&vec1);

  vec2.push(Rc::new(Struct2));

  (vec1, vec2)
}

fn join_names<'a>(v: &[Rc<Trait + 'a>]) -> String {
   v.iter().map(|s| s.name()).collect::<Vec<_>>().connect(" ")
}

fn main() {
  let (vec1, vec2) = build_vectors();

  println!("vec1: {}", join_names(&vec1));
  println!("vec2: {}", join_names(&vec2));
}

请注意,shallow_copy中的闭包使用 clone() 来克隆 Rc。克隆一个 Rc 会创建一个指向相同值的新 Rc(底层值不会被克隆),并将共享引用计数增加1。释放一个 Rc 将使引用计数减少,并且当引用计数降至零时,底层值将被释放。
顺便说一句,我已经将一些函数更改为接受切片引用而不是 Vec 引用,因为这样可以使这些函数更加通用(例如,您也可以从数组中获取切片)。
另外,我不得不对 trait 对象进行生命周期注释,因为如果没有注释,编译器会假定是 'static(即 Trait + 'static),这意味着“实现了 Trait 但不包含任何借用指针(比 'static 短)”,这会导致生命周期错误。

你让我重新对Rust充满信心,之前我有点慌了。非常好的回答。 - user12341234

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接