ВЕК Дженериков Различных Бетонных Типов

У меня есть черта 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> вместо.

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