ВЕК Дженериков Различных Бетонных Типов
У меня есть черта Foo, а конкретные типы A и B ограничены чертой Foo. Я хочу вернуть Vec<Foo>
где Foo может быть конкретным типом A или B, как показано ниже:
trait Foo { }
pub struct A {}
pub struct B {}
impl Foo for A {}
impl Foo for B {}
fn test() -> Vec<Foo> {
let generic_vec: Vec<Foo> = Vec::new();
generic_vec.push(A {});
generic_vec.push(B {});
return generic_vec;
}
Компилятор в настоящий момент выдает ошибку, что размерная черта не реализована для Foo. Я мог бы обернуть Foo в коробку, но я не хочу возвращать Vec объектов-черт из-за накладных расходов времени выполнения, которые они накладывают.
Мне было интересно, есть ли какая-нибудь особенность Rust Generics, которая позволила бы мне возвращать Vec универсальных типов без необходимости использовать объекты-черты.
2 ответа
Вектор - это плотно упакованный массив в памяти, и он требует, чтобы все его элементы занимали одинаковое количество пространства. Размер элементов должен быть известен во время компиляции. Объекты черт не имеют известного размера, поэтому их нельзя хранить в Vec
,
Если вы хотите сохранить вектор элементов, которые либо A
или же B
, ваш лучший вариант - использовать enum:
pub struct A;
pub struct B;
enum Either {
A(A),
B(B),
}
fn main() {
let mut v = vec![];
v.push(Either::A(A));
v.push(Either::B(B));
}
Перечисление Either
имеет размер, равный максимуму размеров A
а также B
, возможно плюс место для дискриминанта, который указывает, что является текущим вариантом. Этот размер известен во время компиляции, поэтому Either
может быть использован в векторе.
Если A
а также B
реализовать общую черту, и вы хотите иметь возможность вызывать методы этой черты для элементов вектора, вы можете реализовать черту на Either
также, перенаправляя все вызовы методов в правильный вариант.
Проблема здесь в том, что нет гарантии, что только потому, что оба A
а также B
воплощать в жизнь Foo
они будут иметь одинаковый размер. С Руста Vec
является однородным, мы должны статически гарантировать, что все элементы в нем имеют размеры.
Решение, таким образом, состоит в том, чтобы объединить типы, связывающие их с признаком.
trait Foo { }
pub struct A {}
pub struct B {}
impl Foo for A {}
impl Foo for B {}
type FooT = Box<dyn Foo>;
fn test() -> Vec<FooT> {
let mut generic_vec: Vec<FooT> = Vec::new();
generic_vec.push(Box::new(A {}));
generic_vec.push(Box::new(B {}));
return generic_vec;
}
Теперь типы могут не иметь одинакового размера, но указатель (Box) будет иметь их, поэтому мы полностью избегаем этой проблемы, хотя и за определенную плату, поскольку для доступа к элементам мы должны разыменовываться.
Обратите внимание, что здесь я определил псевдоним типа FooT
только для удобства чтения, но вы могли бы, конечно, просто использовать Box<dyn Foo>
вместо.