2017-01-12 2 views
2

Я пытаюсь реализовать метод, который выглядит следующим образом:Rc <Trait> Опция <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

Существует диссонанс здесь: 'Вариант ' * собственный * 'T', в то время как' 'Rc является только одним из совладельцев * объекта. Я могу видеть два способа обработки этого: возврат 'None', если есть несколько владельцев или преобразование функции для преобразования из' & Rc 'в' Опция <&T> '. Является первым, что вы хотите? –

+0

Вот что ['try_unwrap'] (https://doc.rust-lang.org/std/rc/struct.Rc.html#method.try_unwrap) для - он гарантирует, что данный' Rc' является только владелец. «concretify'» должен возвращать 'None', если есть несколько владельцев или если' Any' не 'T'. –

+0

Просто проверка :) В этом случае, вы в порядке с «Нет» или вы скорее вернете «Результат», который указывает в поле «Err», является ли проблема несоответствующим типом или несколькими владельцами? –

ответ

3

К сожалению, оказывается, что 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)); 
} 
2

Я не думаю, что можно реализовать функцию concretify, если вы ожидаете, что она вернет исходное значение из Rc; см. this question для чего.

Если вы готовы вернуть клон, это просто:

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))

Отведено: Нет

Playground

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

+2

Боюсь, я не уверен, какая часть связанный вопрос предотвращает переход из 'Box ' в 'T'; вот что ['Box :: downcast()'] (https://doc.rust-lang.org/std/boxed/struct.Box.html#method.downcast). Я просто пытаюсь (на данный момент) преобразовать 'Rc ' '' Box '. (Редактирование вопроса, чтобы уточнить это) –

+0

Хмм, я всегда забываю, что часть '' '' '' '' '' '' находится под 'Box '! Хороший момент, это не ясно, и теперь я менее уверен; Я посмотрю, смогу ли я улучшить свой ответ позже. –