Невозможно создать полиморфный тип, потому что черта не может быть превращена в объект

У меня есть этот упрощенный код Rust:

use std::io::Result;

pub trait PacketBuffer {}

pub trait DnsRecordData {
    fn write<T: PacketBuffer>(&self, buffer: &mut T) -> Result<usize>;
}

pub struct DnsRecord<R: DnsRecordData + ?Sized> {
    pub data: Box<R>,
}

pub struct DnsPacket {
    pub answers: Vec<DnsRecord<dyn DnsRecordData>>,
}

Предполагается, что DnsRecord сможет содержать любую структуру, реализующую черту DnsRecordData, с различными структурами, представляющими A, AAAA, CNAME и т.д.

Это приводит к ошибке:

error[E0038]: the trait 'DnsRecordData' cannot be made into an object
  --> src/lib.rs:14:5
   |
14 |     pub answers: Vec<DnsRecord<dyn DnsRecordData>>,
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait 'DnsRecordData' cannot be made into an object
   |
   = note: method 'write' has generic type parameters

Что меня больше всего смутило, так это то, что, удаляя обобщенные элементы из DnsRecordData::write(), он прекрасно компилируется:

use std::io::Result;

pub trait PacketBuffer {}

pub trait DnsRecordData {
    fn write(&self, buffer: &mut dyn PacketBuffer) -> Result<usize>;
}

pub struct DnsRecord<R: DnsRecordData + ?Sized> {
    pub data: Box<R>,
}

pub struct DnsPacket {
    pub answers: Vec<DnsRecord<dyn DnsRecordData>>,
}

Если кто-нибудь сможет объяснить, что мне не хватает, я буду очень признателен.

Ответ 1

  Предполагается, что DnsRecord сможет содержать любую структуру, реализующую черту DnsRecordData

Это не то, что написано в коде.

Vec<DnsRecord<dyn DnsRecordData>>

Это вектор структуры DnsRecord, содержащей признак DnsRecordData. Если вам нужна "любая структура, реализующая черту DnsRecordData", вам нужен универсальный:

pub struct DnsPacket<D>
where
    D: DnsRecordData,
{
    pub answers: Vec<DnsRecord<D>>,
}

Черты могут быть реализованы, но они также имеют свой собственный тип. Чтобы создать этот тип, черта должна быть объектно-безопасной - Объект черты не является объектно-безопасной ошибкой.

Как говорится в сообщении об ошибке, эта черта не может быть объектом черты, поскольку в методе есть универсальные типы.

Первая ошибка гласит, что DnsRecord требует, чтобы любой тип, с которым он параметризован, реализовывал DnsRecordData. Однако тип объекта черты фактически не реализует это. Обычно вы используете объект признака через ссылку (&dyn DnsRecordData) или блок (Box<dyn DnsRecordData>), оба из которых должны реализовывать признак, предотвращая эту ошибку.

Ответ 2

Ошибка возникает из-за того, что вы не можете создавать объекты черт для DnsRecordData из-за того, что черта не "объектобезопасна". Эта концепция объясняется в разделе объектов черты языка программирования Rust.

В вашем конкретном случае, черта содержит общий метод. Чтобы создать объект признака, компилятор должен синтезировать виртуальную таблицу для признака, содержащую указатель функции для каждого метода, который имеет признак. Но так как у признака есть универсальный метод, он эффективно имеет столько методов, сколько может быть создано для метода, что потенциально бесконечно. Следовательно, вы не можете создать объект черты для DnsRecordData.