12

I've got this simplified Rust code:

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>>,
}

The intention is that DnsRecord should be able to hold any struct implementing the DnsRecordData trait, with the different structs representing A, AAAA, CNAME etc.

This fails with the error:

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

What's got me the most confused is that by removing the generics from DnsRecordData::write(), it compiles just fine:

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>>,
}

If anyone can explain what I'm missing, I'd very much appreciate it.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Emil H
  • 39,840
  • 10
  • 78
  • 97

2 Answers2

12

The error comes from the fact that you can't create trait objects for DnsRecordData due to the trait not being "object-safe". This concept is explained in the trait objects section of The Rust Programming Language.

In your particular case, the trait contains a generic method. To create a trait object, the compiler has to synthesize a vtable for the trait, containing a function pointer for every method the trait has. But because the trait has a generic method, it effectively has as many methods as the method could be instantiated with, which is potentially infinite. Therefore, you cannot make a trait object for DnsRecordData.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Sebastian Redl
  • 69,373
  • 8
  • 123
  • 157
  • "which is potentially infinite" Why though? Aren't the concrete impl known a compile time? – frankelot Apr 16 '23 at 08:15
  • @frankelot The trait might be defined in a library. The dyn trait usage might be in a another library. The type implementing the trait might be in yet another library. If you want to know the full set of types, you'd have to recompile everything whenever the main binary changes. And that's not even considering that Rust actually does support dynamic linking, even though it's unusual. – Sebastian Redl Apr 16 '23 at 20:49
12

The intention is that DnsRecord should be able to hold any struct implementing the DnsRecordData trait

That's not what the code says.

Vec<DnsRecord<dyn DnsRecordData>>

This is a vector of the struct DnsRecord containing the trait DnsRecordData. If you want "any struct implementing the DnsRecordData trait", you need a generic:

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

Traits can be implemented, but they also have their own type. In order to create this type, the trait needs to be object-safe - Trait Object is not Object-safe error.

As the error message states, this trait cannot be a trait object because there are generic types on the method.

The first error states that DnsRecord requires that whatever type it is parameterized with must implement DnsRecordData. However, the type of the trait object doesn't actually implement that. Normally, you'd use a trait object via a reference (&dyn DnsRecordData) or a box (Box<dyn DnsRecordData>), both of which should implement the trait, preventing this error.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Is there a way to do this without such verbose generic setting-up syntax? e.g. just in the field itself `pub answers: Vec>` – theonlygusti Feb 13 '23 at 13:04
  • @theonlygusti no, because the generic type parameter isn't an implementation detail that can be hidden away, it's part of the public API of the type. You could use `pub struct DnsPacket { pub answers: Vec> }`, but I generally advocate for using `where` clauses. – Shepmaster Apr 14 '23 at 21:11