Rc<Trait> для Option<T>?

Я пытаюсь реализовать метод, который выглядит следующим образом:

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 Вот.

2 ответа

Решение

К сожалению, похоже, что API Rc не хватает необходимого метода, чтобы иметь возможность владеть обернутым типом, когда он !Sized,

Единственный метод, который может вернуть элемент интерьера Rc является Rc::try_unwrapОднако возвращается Result<T, Rc<T>> что требует, чтобы T быть Sized,

Для того, чтобы делать то, что вы хотите, вам нужно иметь метод с подписью: 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: ?Sized мандаты, которые T быть последним полем

так что вообще мало места для улучшения.


Тем не менее, в вашем конкретном случае, безусловно, можно улучшить общую ситуацию. Это, конечно, требует 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));
}

Я не думаю, что возможно реализовать 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);
}

Это выводит:

Вернулся: некоторые (Foo(42))

Вернулся: нет

Детская площадка

Если вы хотите что-то более "подвижное", а создание ваших ценностей обходится дешево, вы также можете сделать пустышку, используйте downcast_mut() вместо downcast_ref(), а потом std::mem::swap с манекеном

Другие вопросы по тегам