Как создать Box<T>, когда T является объектом-признаком?

У меня есть следующий код

extern crate rand;
use rand::Rng;

pub struct Randomizer {
    rand: Box<Rng>,
}

impl Randomizer {
    fn new() -> Self {
        let mut r = Box::new(rand::thread_rng()); // works
        let mut cr = Randomizer { rand: r };
        cr
    }

    fn with_rng(rng: &Rng) -> Self {
        let mut r = Box::new(*rng); // doesn't work
        let mut cr = Randomizer { rand: r };
        cr
    }
}

fn main() {}

Жалуется что

error[E0277]: the trait bound `rand::Rng: std::marker::Sized` is not satisfied
  --> src/main.rs:16:21
   |
16 |         let mut r = Box::new(*rng);
   |                     ^^^^^^^^ `rand::Rng` does not have a constant size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `rand::Rng`
   = note: required by `<std::boxed::Box<T>>::new`

Я не понимаю, почему это требует Sized на Rng когда Box<T> не навязывает это T,

3 ответа

Решение

Подробнее о Sized trait and bound - это довольно особенная черта, которая неявно добавляется к каждой функции, поэтому вы не видите ее в прототипе для Box::new:

fn new(x: T) -> Box<T>

Обратите внимание, что это занимает x по значению (или перемещению), поэтому вам нужно знать, насколько велика возможность вызова функции.

В отличие от Box сам тип не требует Sized; он использует (снова особую) границу черты ?Sized, что означает "отказаться от использования по умолчанию" Sized оценка ":

pub struct Box<T> where T: ?Sized(_);

Если вы посмотрите, есть один способ создать Box с нестандартным типом:

impl<T> Box<T> where T: ?Sized
....
    unsafe fn from_raw(raw: *mut T) -> Box<T>

поэтому из небезопасного кода вы можете создать его из необработанного указателя. С этого момента все нормальные вещи работают.

Проблема на самом деле довольно проста: у вас есть объект черты, и вам известны только две вещи:

  • его список доступных методов
  • указатель на его данные

Когда вы запрашиваете перемещение этого объекта в другое место в памяти (здесь, в куче), вы упускаете одну важную информацию: его размер.

Как вы узнаете, сколько памяти должно быть зарезервировано? Сколько битов нужно переместить?

Когда объект Sizedэта информация известна во время компиляции, поэтому компилятор "внедряет" ее для вас. Однако в случае признака-объекта эта информация неизвестна (к сожалению), и, следовательно, это невозможно.

Было бы весьма полезно сделать эту информацию доступной и иметь доступ к полиморфному движению / клону, но этого еще не существует, и я пока не помню ни одного предложения по нему, и я не знаю, какой будет стоимость (с точки зрения обслуживания, штраф за время выполнения, ...).

Я также хочу опубликовать ответ, что один из способов справиться с этой ситуацией

fn with_rng<TRand: Rng>(rng: &TRand) -> Self {
    let r = Box::new(*rng);
    Randomizer { rand: r }
}

Мономорфизм Руста создаст необходимую реализацию with_rng замена TRand по конкретному типу размеров. Кроме того, вы можете добавить границу черты, требующую TRand быть Sized,

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