Problems caused by async trait

I'm writing code with stable async trait in rust 1.75 and I'm getting an error:
error[E0391]: cycle detected when checking effective visibilities

environment:

Rust version:

cargo 1.75.0 (1d8b05cdd 2023-11-20)
rustup 1.26.0 (5af9b9484 2023-04-05)
info: This is the version for the rustup toolchain manager, not the rustc compiler.
info: The currently active rustc version is rustc 1.75.0 (82e1608df 2023-12-21)

System version:

OS 名称: Microsoft Windows 10 专业版
OS 版本: 10.0.19045 暂缺 Build 19045
OS 制造商: Microsoft Corporation

Project struct:

├── Cargo.lock
├── Cargo.toml
└── src
   ├── main.rs
   ├── common
   │   ├── mod.rs
   │   └── simple.rs
   ├── models
   │   ├── entity.rs
   │   └── mod.rs
   └── repository
       ├── db.rs
       └── mod.rs

Dependencies:

[dependencies]
sqlx = { version = "0.7.3", features = ["postgres"] }
tokio = { version = "1.35.1", features = ["rt", "rt-multi-thread", "macros"] }

Code content:

common/simple.rs

use std::future::Future;

pub trait Simple {
    fn method_call(&self, db: &sqlx::Pool<sqlx::Postgres>) -> impl Future<Output=Result<(), sqlx::Error>> + Send;

    fn method_call_one(&self, db: &sqlx::Pool<sqlx::Postgres>) -> Result<(), sqlx::Error>;
}

common/mod.rs

pub use simple::Simple;

mod simple;

models/entity.rs

use sqlx::{Error, Pool, Postgres};

use crate::common::Simple;

#[derive(Default, Debug)]
pub struct Entity {
    // name: Option<String>,
}

impl Simple for Entity {
    async fn method_call(&self, _db: &Pool<Postgres>) -> Result<(), Error> {
        return Ok(());
    }

    fn method_call_one(&self, _db: &Pool<Postgres>) -> Result<(), Error> {
        return Ok(());
    }
}

models/mod.rs

pub mod entity;

repository/db.rs

use std::time::Duration;

use sqlx::postgres::PgPoolOptions;

use crate::common::Simple;
use crate::models::entity::Entity;

pub async fn connect() -> Result<sqlx::Pool<sqlx::Postgres>, sqlx::Error> {
    let pool = connect_by_pool().await?;
    let _scheme: Vec<Box<dyn Simple>> = vec![
        Box::new(Entity::default()),
        Box::new(Entity::default()),
        Box::new(Entity::default()),
        Box::new(Entity::default()),
    ];

    // for _x in _scheme {
    // let _ = x.method_call(&pool).await?;
    // let _ = x.method_call_one(&pool).await?;
    // }

    return Ok(pool);
}

async fn connect_by_pool() -> Result<sqlx::Pool<sqlx::Postgres>, sqlx::Error> {
    return PgPoolOptions::new()
        .min_connections(4)
        .max_connections(16)
        .max_lifetime(Duration::from_millis(60000))
        .connect("postgres://postgres:postgres@172.17.0.1:5432/postgres")
        .await;
}

repository/mod.rs

pub mod db;

main.rs

use crate::repository::db;

mod models;
mod common;
mod repository;


#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
    let _pool = db::connect().await?;

    return Ok(());
}

After doing cargo check, I get the error:

error[E0391]: cycle detected when checking effective visibilities
  |
note: ...which requires computing type of `repository::db::connect::{opaque#0}`...
 --> src\repository\db.rs:8:1
  |
8 | pub async fn connect() -> Result<sqlx::Pool<sqlx::Postgres>, sqlx::Error> {
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...which requires computing type of opaque `repository::db::connect::{opaque#0}`...
 --> src\repository\db.rs:8:1
  |
8 | pub async fn connect() -> Result<sqlx::Pool<sqlx::Postgres>, sqlx::Error> {
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...which requires type-checking `repository::db::connect`...
 --> src\repository\db.rs:8:1
  |
8 | pub async fn connect() -> Result<sqlx::Pool<sqlx::Postgres>, sqlx::Error> {
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  = note: ...which again requires checking effective visibilities, completing the cycle
note: cycle used when checking item types in module `models::entity`
 --> src\models\mod.rs:1:1
  |
1 | pub mod entity;
  | ^^^^^^^^^^^^^^
  = note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information

For more information about this error, try `rustc --explain E0391`.

But

I tried modifying the code:
common/simple.rs

use std::future::Future;

pub trait Simple {
    // origin
    //     fn method_call(&self, db: &sqlx::Pool<sqlx::Postgres>) -> impl Future<Output=Result<(), sqlx::Error>> + Send;

    // now
    fn method_call(&self, db: &sqlx::Pool<sqlx::Postgres>) -> Result<(), sqlx::Error>;

    fn method_call_one(&self, db: &sqlx::Pool<sqlx::Postgres>) -> Result<(), sqlx::Error>;
}

models/entity.rs

use sqlx::{Error, Pool, Postgres};

use crate::common::Simple;

#[derive(Default, Debug)]
pub struct Entity {
    // name: Option<String>,
}

impl Simple for Entity {
    // origin
    //     async fn method_call(&self, _db: &Pool<Postgres>) -> Result<(), Error> {

    // now
    fn method_call(&self, _db: &Pool<Postgres>) -> Result<(), Error> {
        return Ok(());
    }

    fn method_call_one(&self, _db: &Pool<Postgres>) -> Result<(), Error> {
        return Ok(());
    }
}

Compile pass

please at least show the code snippet that triggers the error.

emm... Please wait, im edit now...

Okay, can you help me take a look?

I cannot reproduce the error. your code compiles just fine. check this rustexplorer link

When the code is inside a file, the compilation does pass, but when the code is split into different modules, the compilation gets an error

I copy-pasted your code into separated files as your example, still compiles fine. there might be something wrong with your environment. try build the code on another machine or operating system.

Thank you very much for your help, my system is Windows 10 professional x64, I tried to install a new set of rust environment with Ubuntu 22.04 in WSL, after doing cargo check, I get the same error

I think I've seen a similar looking error before that's already fixed on nightly. Have you tested if the error still appears when you compile with an up-to-date nightly compiler?

1 Like

Thanks for the suggestion, I switched to the nightly version with rustup default nightly on ubuntu 22.04 and tried to use cargo check, only to get more errors.

error[E0038]: the trait `simple::Simple` cannot be made into an object
  --> src/repository/db.rs:10:17
   |
10 |     let scheme: Vec<Box<dyn Simple>> = vec![
   |                 ^^^^^^^^^^^^^^^^^^^^ `simple::Simple` cannot be made into an object
   |
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
  --> src/common/simple.rs:4:63
   |
3  | pub trait Simple {
   |           ------ this trait cannot be made into an object...
4  |     fn method_call(&self, db: &sqlx::Pool<sqlx::Postgres>) -> impl Future<Output=Result<(), sqlx::Error>> + Send;
   |                                                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...because method `method_call` references an `impl Trait` type in its return type
   = help: consider moving `method_call` to another trait
   = help: only type `models::entity::Entity` implements the trait, consider using it directly instead

error[E0038]: the trait `simple::Simple` cannot be made into an object
  --> src/repository/db.rs:11:9
   |
11 |         Box::new(Entity::default()),
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `simple::Simple` cannot be made into an object
   |
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
  --> src/common/simple.rs:4:63
   |
3  | pub trait Simple {
   |           ------ this trait cannot be made into an object...
4  |     fn method_call(&self, db: &sqlx::Pool<sqlx::Postgres>) -> impl Future<Output=Result<(), sqlx::Error>> + Send;
   |                                                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...because method `method_call` references an `impl Trait` type in its return type
   = help: consider moving `method_call` to another trait
   = help: only type `models::entity::Entity` implements the trait, consider using it directly instead
   = note: required for the cast from `Box<Entity>` to `Box<dyn simple::Simple>`

error[E0038]: the trait `simple::Simple` cannot be made into an object
  --> src/repository/db.rs:12:9
   |
12 |         Box::new(Entity::default()),
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `simple::Simple` cannot be made into an object
   |
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
  --> src/common/simple.rs:4:63
   |
3  | pub trait Simple {
   |           ------ this trait cannot be made into an object...
4  |     fn method_call(&self, db: &sqlx::Pool<sqlx::Postgres>) -> impl Future<Output=Result<(), sqlx::Error>> + Send;
   |                                                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...because method `method_call` references an `impl Trait` type in its return type
   = help: consider moving `method_call` to another trait
   = help: only type `models::entity::Entity` implements the trait, consider using it directly instead
   = note: required for the cast from `Box<Entity>` to `Box<dyn simple::Simple>`

error[E0038]: the trait `simple::Simple` cannot be made into an object
  --> src/repository/db.rs:13:9
   |
13 |         Box::new(Entity::default()),
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `simple::Simple` cannot be made into an object
   |
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
  --> src/common/simple.rs:4:63
   |
3  | pub trait Simple {
   |           ------ this trait cannot be made into an object...
4  |     fn method_call(&self, db: &sqlx::Pool<sqlx::Postgres>) -> impl Future<Output=Result<(), sqlx::Error>> + Send;
   |                                                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...because method `method_call` references an `impl Trait` type in its return type
   = help: consider moving `method_call` to another trait
   = help: only type `models::entity::Entity` implements the trait, consider using it directly instead
   = note: required for the cast from `Box<Entity>` to `Box<dyn simple::Simple>`

error[E0038]: the trait `simple::Simple` cannot be made into an object
  --> src/repository/db.rs:14:9
   |
14 |         Box::new(Entity::default()),
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `simple::Simple` cannot be made into an object
   |
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
  --> src/common/simple.rs:4:63
   |
3  | pub trait Simple {
   |           ------ this trait cannot be made into an object...
4  |     fn method_call(&self, db: &sqlx::Pool<sqlx::Postgres>) -> impl Future<Output=Result<(), sqlx::Error>> + Send;
   |                                                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...because method `method_call` references an `impl Trait` type in its return type
   = help: consider moving `method_call` to another trait
   = help: only type `models::entity::Entity` implements the trait, consider using it directly instead
   = note: required for the cast from `Box<Entity>` to `Box<dyn simple::Simple>`

error[E0038]: the trait `simple::Simple` cannot be made into an object
  --> src/repository/db.rs:10:40
   |
10 |       let scheme: Vec<Box<dyn Simple>> = vec![
   |  ________________________________________^
11 | |         Box::new(Entity::default()),
12 | |         Box::new(Entity::default()),
13 | |         Box::new(Entity::default()),
14 | |         Box::new(Entity::default()),
15 | |     ];
   | |_____^ `simple::Simple` cannot be made into an object
   |
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
  --> src/common/simple.rs:4:63
   |
3  | pub trait Simple {
   |           ------ this trait cannot be made into an object...
4  |     fn method_call(&self, db: &sqlx::Pool<sqlx::Postgres>) -> impl Future<Output=Result<(), sqlx::Error>> + Send;
   |                                                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...because method `method_call` references an `impl Trait` type in its return type
   = help: consider moving `method_call` to another trait
   = help: only type `models::entity::Entity` implements the trait, consider using it directly instead
   = note: this error originates in the macro `vec` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0038`.

Obviously, this is beyond my comprehension at the moment, and I'm going to fix it in a different way, such as using synchronization or restructuring the directory structure, and I would appreciate the help

Indeed, as this error message tells you, traits with impl Trait return types (or async fn) are not object safe. If you need trait objects, for async fn the most convenient option is probably still async_trait - Rust, for other traits in return values, you’d need to use e.g. Box<dyn Trait> manually.

See also the blog post on the feature

If common use-cases don’t involve trait objects and you don’t want to change the trait itself for efficientcy of such use-cases, it can also always be an option to only wrap around the trait to make it object safe just for the use-cases you need; see also this answer I wrote in a different thread about object safety today:

2 Likes

Thanks a lot, I'll try to use async_trait crate to solve my problem

A post was split to a new topic: How to write mock async functions without using trait objects

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.