【问题标题】:Is it possible to map an Rc<T> to get an Rc<Subpart-of-T>?是否可以映射一个 Rc<T> 来获得一个 Rc<Subpart-of-T>?
【发布时间】:2019-01-15 23:38:46
【问题描述】:

我有一个Rc&lt;Option&lt;T&gt;&gt;,但需要从中获取一个Rc&lt;T&gt;。比如:

let rc_option: Rc<Option<T>> = Rc::new(Ok(value));
let ok_value: Rc<T> = rc_option.map(|option| option.unwrap());

这甚至是遥不可及的吗?这似乎是有道理的,因为Rc 可以只增加它在内部为新映射值所拥有的计数器,但我找不到任何文档。

【问题讨论】:

  • 如果内部Option&lt;T&gt;None,你会得到什么?
  • "这似乎应该是有意义的",一点也不。此外,如果您最终拥有 Option&lt;T&gt; 是有原因的,那么为什么要删除 Option 呢?如果您不需要它,请不要使用Option
  • Re: 选项,这只是一个例子,想象一下它试图在一个总是存在的结构中获取一个字段。
  • @Stargateur:使用 C++ shared_ptr 可以将 shared_ptr 获取到字段或数组元素,并共享相同的“计数器”。
  • @MatthieuM。我看不出理性,这似乎很不安全,基本上如果最后一个shared_ptr不包含好的ptr,你如何释放ptr?这似乎只是为了打破shared_ptr 的api。可能!=应该

标签: rust reference-counting


【解决方案1】:

简答

不,不可能从 Rc&lt;Option&lt;T&gt;&gt; 创建一个 Rc&lt;T&gt; 以使后者仍然存在。但是,可以从Rc&lt;Option&lt;T&gt;&gt; 创建一个Rc&lt;&amp;T&gt;,同时仍然保留后一个变量。

长答案

如果您尝试在Rc&lt;Option&lt;T&gt;&gt; 中创建一个拥有T 的新Rc&lt;T&gt;,则必须使用原始Rc&lt;Option&lt;T&gt;&gt;。您也不能拥有Rc&lt;Option&lt;T&gt;&gt; 的多个实例,因为这样您就在指针仍然存在时移动了共享值,这是非常不安全的。

但是有一种方法可以安全地做到这一点!使用Rc::try_unwrap,您可以尝试将值移出,但如果存在原始Rc 的多个实例,则会返回错误。 请记住,您还必须处理Option&lt;T&gt; 最终成为None 的情况。

这是一个例子:

let rc_option: Rc<Option<T>> = Rc::new(Some(value));

match Rc::try_unwrap(rc_option) {
    Ok(option) => {
        match option {
            Some(t) => {
                let ok_value: Rc<T> = Rc::new(t);
                // Do something with ok_value
            }
            None => {
                // Do something else here
            }
        }
    }
    Err(rc_option) => {
        // There are multiple owners, do something else here
    }
}

如果您想保留原件,您可以这样做:

match &*rc_option {
    Some(ref t) => {
        let ok_ref: Rc<&T> = Rc::new(t);
    }
    None => { /* Do something else, there's no internal value */ }
}

编辑:正如 Chronial 提到的,请注意 ok_ref 不能比 rc_option 寿命长(因为它是对 rc_option 的引用),这可能不是您想要发生的。

【讨论】:

  • 应该注意的是,新的 Rc 的生命周期将绑定到原始的 Rc(即它不能超过它),这可能不是你想要的。
  • 你是对的。我会更新我的答案来提及这一点。
  • 谢谢,这有助于我理解。
  • &amp;TCopy,因此将其填充到新的Rc 中确实没有意义,因为它不能延长原始T 的生命周期。您不妨在match 中使用t
  • 我只将&amp;T 放入Rc(在此示例中),因为他们希望将值映射到Rc。我展示了如何生成一个Rc,其中包含原始值而不消耗它,但也受到原始值的生命周期的限制。
【解决方案2】:

与 Rc 无关。

Rc 无法做到这一点,因为它的内存布局:

//  Equivalence:
struct RcBox<T> {
    strong: AtomicUsize,
    weak: AtomicUsize,
    data: T,
};

struct Rc<T> {
    ptr: *const RcBox<T>,
};

因此,计数器应位于 T 旁边,因此您不能在两个不同的元素之间共享计数器。


可能有替代方案。

从内存布局的角度来看,创建一个替代FlexRc 是完全可以接受的:

struct Counters {
    strong: AtomicUsize,
    weak: AtomicUsize, // if support for FlexWeak is desired.
    ptr: *mut (),
    drop: fn(*mut ()),
}

struct FlexRc<T> {
    counters: *mut Counters,
    ptr: *const T,
}

而且这个理论上可以允许映射...但是在它上面创建一个安全的接口可能并不容易。

如何防止用户在map 中返回不相关的生命周期?保证返回引用的生命周期超过flex 的生命周期就足够安全了吗?

fn fool(flex: FlexRc<Option<i32>>) -> FlexRc<i32> {
    let i = 3;
    flex.map(|_| &i)
}

【讨论】:

  • 嗯,是的,如果您可以强制返回 Rc 中值的子部分,这是否安全?就像确保它只是一个值的字段,如果它是一个结构?不知道你会怎么做。
  • @CallumRogers:这将是安全的......虽然我刚刚意识到它会泄漏:) 计数器需要知道原始指针并具有运行其析构函数的函数。不过,我不知道如何正确地执行终身关系。
  • @CallumRogers:因此,将析构函数添加到Counters,因为它是数据的实际所有者。对于生命周期关系,我认为我们可以限制 lambda 接受的生命周期:FnOnce(&amp;'a T) -&gt; &amp;'a U... 但尚不清楚它是否足够。
【解决方案3】:

正如 ThatOneDeveloper 在 their answer 中所述,标准库不支持该功能。您可以自己实现这样的功能:

use std::ops::Deref;

#[derive(Clone)]
struct RcSome<T>(Rc<Option<T>>);

impl<T> RcSome<T> {
    fn from(rc: &Rc<Option<T>>) -> RcSome<T> {
        RcSome(rc.clone())
    }
}

impl<T> Deref for RcSome<T> {
    type Target = T;
    fn deref(&self) ->  &T {
        self.0.as_ref().as_ref().unwrap()
    }
}

那么你就可以这样做了

let rc_option: Rc<Option<T>> = Rc::new(Some(value));
let ok_value: RcSome<T> = RcSome::from(&rc_option);

请注意,如果rc_option 包含None,这将引发恐慌。但是ok_value 现在的行为类似于Rc&lt;T&gt; - 即您可以clone() 它并执行ok_value.some_method_of_T()ok_value 也不与 rc_option 共享生命周期,因此它的寿命会更长。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-06-14
    • 2023-01-20
    • 2019-12-13
    • 1970-01-01
    • 2015-10-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多