Как создать 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
,