2

次のようなメソッドを実装しようとしています。

fn concretify<T: Any>(rc: Rc<Any>) -> Option<T> {
    Rc::try_unwrap(rc).ok().and_then(|trait_object| {
        let b: Box<Any> = unimplemented!();
        b.downcast().ok().map(|b| *b)
    })
}

ただし、try_unwrapトレイト オブジェクトでは機能しません (サイズが決まっていないため、これは理にかなっています)。Rc<Any>次に考えたのは、直接アンラップする関数を見つけようとすることでしたBox<Any>。私が見つけることができる最も近いものは

if Rc::strong_count(&rc) == 1 {
    Some(unsafe {
        Box::from_raw(Rc::into_raw(rc))
    })
} else {
    None
}

ただし、Rc::into_raw()に含まれる型が である必要があるRcようで、理想的にはブロックSizedを使用する必要はありません。unsafe

これを実装する方法はありますか?

Playground Link、ここの実装を探していrc_to_boxます。

4

2 に答える 2

3

残念ながら、 の API にRcは、 の場合にラップされた型の所有権を取得できるようにするために必要なメソッドが欠けているようです!Sized

a の内部アイテムを返すメソッドは だけRcですが、 beが必要なメソッドがRc::try_unwrap返されます。Result<T, Rc<T>>TSized

あなたが望むことをするためには、署名付きのメソッドが必要です:Rc<T> -> Result<Box<T>, Rc<T>>であることがTでき!Sized、そこから呼び出しを抽出Box<Any>して実行できdowncastます。

Rcただし、実装方法により、この方法は不可能です。以下は、 の簡略版ですRc

struct RcBox<T: ?Sized> {
    strong: Cell<usize>,
    weak: Cell<usize>,
    value: T,
}

pub struct Rc<T: ?Sized> {
    ptr: *mut RcBox<T>,
    _marker: PhantomData<T>,
}

したがって、Boxあなたが抜けられるのRc<T>はだけですBox<RcBox<T>>

ここでは設計が厳しく制限されていることに注意してください。

  • 単一割り当てでは、3 つの要素すべてが単一にあることが義務付けられています。struct
  • T: ?SizedT最後のフィールドである命令

そのため、一般的に改善の余地はほとんどありません。


ただし、特定のケースでは、一般的な状況を改善することは間違いなく可能です。もちろん、コードが必要unsafeです。また、 でかなりうまく機能しますがRc、 での実装Arcは潜在的なデータ競合によって複雑になります。

ああ...そしてコードはそのまま提供され、保証はありません;)

use std::any::Any;
use std::{cell, mem, ptr};
use std::rc::Rc;

struct RcBox<T: ?Sized> {
    strong: cell::Cell<usize>,
    _weak: cell::Cell<usize>,
    value: T,
}

fn concretify<T: Any>(rc: Rc<Any>) -> Option<T> {
    //  Will be responsible for freeing the memory if there is no other weak
    //  pointer by the end of this function.
    let _guard = Rc::downgrade(&rc);

    unsafe {
        let killer: &RcBox<Any> = {
            let killer: *const RcBox<Any> = mem::transmute(rc);
            &*killer 
        };

        if killer.strong.get() != 1 { return None; }

        //  Do not forget to decrement the count if we do take ownership,
        //  as otherwise memory will not get released.
        let result = killer.value.downcast_ref().map(|r| {
            killer.strong.set(0);
            ptr::read(r as *const T)
        });

        //  Do not forget to destroy the content of the box if we did not
        //  take ownership
        if result.is_none() {
            let _: Rc<Any> = mem::transmute(killer as *const RcBox<Any>);
        }

        result
    }
}

fn main() {
    let x: Rc<Any> = Rc::new(1);
    println!("{:?}", concretify::<i32>(x));
}
于 2017-01-12T19:07:10.050 に答える
2

元の値を;concretifyの外に戻すことを期待している場合、関数を実装することはできないと思います。理由については、この質問Rcを参照してください。

クローンを返す場合は、簡単です。

fn concretify<T: Any+Clone>(rc: Rc<Any>) -> Option<T> {
    rc.downcast_ref().map(Clone::clone)
}

ここにテストがあります:

#[derive(Debug,Clone)]
struct Foo(u32);

#[derive(Debug,Clone)]
struct Bar(i32);

fn main() {
    let rc_foo: Rc<Any> = Rc::new(Foo(42));
    let rc_bar: Rc<Any> = Rc::new(Bar(7));
    
    let foo: Option<Foo> = concretify(rc_foo);
    println!("Got back: {:?}", foo);
    let bar: Option<Foo> = concretify(rc_bar);
    println!("Got back: {:?}", bar);
}

これは以下を出力します:

戻りました: Some(Foo(42))

戻った:なし

遊び場

もっと「動きのある」ものが必要で、値の作成が安価な場合は、ダミーを作成し、downcast_mut()代わりに をdowncast_ref()使用してから、ダミーを使用することもできますstd::mem::swap

于 2017-01-12T17:34:23.483 に答える