如何表示一个可以是Rc<T>或Weak<T>的字段?

3

I'd like to have a field in struct like this:

struct Foo<T> {
    bar: Smart<T>
}

在Rust中,如何以惯用方式实现这一点,而无需创建自定义枚举?其中bar可以是Rc<TWeak<T>,具体取决于不同Foo实例之间的“所有权关系”。请注意保留HTML标签。


2
我不认为存在这样的类型,但我不能确定这是否是一个好主意:弱引用(weak)与引用计数(rc)允许建模所有权关系。例如,父对象拥有子对象(Rc<Child>),而子对象指向其所有者:Weak<Parent>。无法区分这种基本差异可能会令人困惑。 - undefined
3个回答

2
除了创建自定义枚举之外,Rust 中是否有其他惯用的方法来完成这个操作?就像 Rust 中大多数其他“这样或那样”的选择一样,enum 是惯用的方式。

1
OP的问题(如果我理解正确)是:标准库中是否有这样的抽象。手动编写这样的枚举可能需要大量的样板代码。 - undefined
@FrenchBoiethios 标准库中没有提供类似的功能。 - undefined

2

Peter的回答建议,stdlib库中没有这样的抽象。您可以定义一个小枚举来处理两种情况:

use std::rc::{Rc, Weak};

enum MaybeStrong<T> {
    Strong(Rc<T>),
    Weak(Weak<T>),
}

impl<T> MaybeStrong<T> {
    fn get(&self) -> Option<Rc<T>> {
        match self {
            MaybeStrong::Strong(t) => Some(Rc::clone(t)),
            MaybeStrong::Weak(w) => w.upgrade(),
        }
    }
}

struct Foo<T> {
    bar: MaybeStrong<T>
}

impl<T> Foo<T> {
    fn from_weak(inner: Weak<T>) -> Self {
        Self { bar: MaybeStrong::Weak(inner) }
    }

    fn from_strong(inner: Rc<T>) -> Self {
        Self { bar: MaybeStrong::Strong(inner) }
    }

    fn say(&self) where T: std::fmt::Debug {
        println!("{:?}", self.bar.get())
    }
}

fn main() {
    let inner = Rc::new("foo!");
    Foo::from_weak(Rc::downgrade(&inner)).say();
    Foo::from_strong(inner).say();
}

如果 self.bar() 是由一个强指针创建的,它将始终返回 Some,如果是一个 Weak 并且它是悬空的,则返回 None。请注意,由于 get() 需要先创建一个拥有的 Rc,该方法无法返回 &T(包括 Option<&T>),因为 &T 可能会悬挂。这也意味着在处理过程中,所有使用 bar() 的用户都将拥有一个内部值的强引用计数,使其可以安全地在任何情况下使用。

由于get()总是意味着内存写入(增加/减少计数器),因此提供一个原地的apply(f: FnOnce(Option<&T>) -> R) -> R方法可能会很有价值,在Strong情况下不需要执行这些写入操作。特质限定将需要for<'a> - undefined

2
这种结构通常被称为“Either”,有一个库看起来可以解决一些常见的用例:https://docs.rs/either/1.5.3/either/ 然后你可以这样写:
struct Foo<T> {
    bar: Either<Weak<T>, Rc<T>>
}

以下是一个获取 Option<Rc<T>> 的示例函数:

impl <T> Foo<T> {
  fn get_rc(self) -> Option<Rc<T>> {
     self.bar
       .map_left( |weak| weak.upgrade() )
       .map_right( |v| Some(v) )
       .into_inner()
  }
}

然后可以像这样使用它:
fn main() {
    let x = Rc::new(1);
    let f_direct = Foo{ bar:Either::Right(x.clone()) };
    println!("f_direct.get_rc() = {:?}", f_direct.get_rc());
    let f_weak = Foo{ bar:Either::Left(Rc::downgrade(&x)) };
    println!("f_weak.get_rc() = {:?}", f_weak.get_rc());
}

在playground中查看完整示例:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c20faaa46277550e16a3d3b24f3d1750

注:此链接提供了一个Rust语言的代码示例。

@MatthieuM。我原本以为这两个都不会在playgrounds的crate列表中,但事实证明它们确实在那里。所以我修复了代码(只有一个小错误),并提供了一个完整的示例链接。谢谢。 - undefined

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