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
с манекеном