How to use supertrait in structs

Hi Everyone

I have a Repository supertrait and a few subtrait for repositories for different domains. There are concrete implementations as struct in several database types,like Inmemory and MySQL. I want to have a single struct UnitOfWork which can store any concrete implementation, as long as it implements the Repository trait and the subtrait can be specified as generic type.

I have implemented it with:

trait Repository {}

trait InvoiceRepository: Repository {}

struct MysqlInvoiceRepository {}

impl InvoiceRepository for MysqlInvoiceRepository {}
impl Repository for MysqlInvoiceRepository {}

struct InmemoryInvoiceRepository {}

impl InvoiceRepository for InmemoryInvoiceRepository {}
impl Repository for InmemoryInvoiceRepository {}

trait OrderRepository: Repository {}

struct MysqlOrderRepository {}

impl OrderRepository for MysqlOrderRepository {}
impl Repository for MysqlOrderRepository {}

struct InmemoryOrderRepository {}

impl OrderRepository for InmemoryOrderRepository {}
impl Repository for InmemoryOrderRepository {}

struct UoW<T: Repository> {
    repository: Box<T>,
}

enum RepoType {
    MySQL,
    InMemory,
}

fn create_uow<T: InvoiceRepository + 'static>(
    repo_type: RepoType,
) -> UoW<Box<dyn InvoiceRepository>> {
    match repo_type {
        RepoType::MySQL => UoW {
            repository: Box::new(MysqlInvoiceRepository {}),
        },

        RepoType::InMemory => Box::new(InmemoryInvoiceRepository {}),
    }
}

But I can not get rid of the error

error[E0277]: the trait bound `std::boxed::Box<(dyn uow::InvoiceRepository + 'static)>: uow::Repository` is not satisfied
  --> crates/ddd/src/uow.rs:38:6
   |
38 | ) -> UoW<Box<dyn InvoiceRepository>> {
   |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `uow::Repository` is not implemented for `std::boxed::Box<(dyn uow::InvoiceRepository + 'static)>`
   |
   = help: the following other types implement trait `uow::Repository`:
             uow::InmemoryInvoiceRepository
             uow::InmemoryOrderRepository
             uow::MysqlInvoiceRepository
             uow::MysqlOrderRepository
note: required by a bound in `uow::UoW`
  --> crates/ddd/src/uow.rs:27:15
   |
27 | struct UoW<T: Repository> {
   |               ^^^^^^^^^^ required by this bound in `UoW`

Can you give me some pointers for a solution?

Hey @mrijken!

So, there's a couple things going on here, but the main problem is the return type:

UoW<Box<dyn InvoiceRepository>>

It shouldn't include the Box wrapper in it and should just be UoW<dyn InvoiceRepository>

That's because the Boxing isn't part of the generic parameter, the type should just be the repository.
And then repository is stored in a box internally.

struct UoW<T: Repository> {
    repository: Box<T>,
}

Otherwise, you're trying to store a Box<Box<dyn InvoiceRepository>>.
That's what the (admittedly hard to decipher) error message is saying:
std::boxed::Box<...> doesn't implement uow::Repository, so it doesn't fit the bill for T.
What's inside the box doesn't matter.


But even after fixing this, there's still one other problem.
By "default" generic parameters must be concrete types (more specifically they must be Sized).
So T: Repository will only accept concrete (Sized) types; and dyn InvoiceRepository isn't concrete (Sized), so it still wouldn't compile.

This took me a while to figure out myself! But it's an easy fix when you know the magic word.
Just change your generic parameter to this:

struct UoW<T: Repository + ?Sized> {

This tells the compiler to allow both sized and unsized types like dyn X as type parameters.

This restriction exists because normally you can't store dyn types on their own, but since you never store T directly (and instead store Box<T>) there's no problem with relaxing the Sized requirement.


There's one other minor thing that probably just got changed in the course of fighting the compiler, but the match arms in create_uow aren't correct. The RepoType::InMemory arm should be returning a UoW, but it isn't.

2 Likes

See this playground for a working example.

Note that I removed some of your definitions that weren't related to the problem
so it's easier to read!

1 Like

In the return type I added Box because I have got the error without Box:

error[E0277]: the size for values of type `(dyn uow::InvoiceRepository + 'static)` cannot be known at compilation time

So the solution was not adding the Box to the return type, but the ?Sized to the struct.

Thanks!