无法在生锈柴油中使用通用参数实现特征

Cannot implement trait with generic param in rust diesel

我定义了一个名为 exists_by_id_and_password 的特征。我不想用具体的数据库后端来实现它,所以一些通用参数被添加到 DB 结构中。但编译器报错:

type mismatch resolving `<C as Connection>::Backend == Pg`
expected type parameter `B`
           found struct `Pg`
required because of the requirements on the impl of `LoadQuery<C, bool>` for `diesel::query_builder::SelectStatement<(), query_builder::select_clause::SelectClause<Exists<diesel::query_builder::SelectStatement<table, query_builder::select_clause::DefaultSelectClause, query_builder::distinct_clause::NoDistinctClause, query_builder::where_clause::WhereClause<And<diesel::expression::operators::Eq<columns::username, diesel::expression::bound::Bound<diesel::sql_types::Text, std::string::String>>, diesel::expression::operators::Eq<columns::password, diesel::expression::bound::Bound<diesel::sql_types::Text, std::string::String>>>>>>>>`

很奇怪提到的错误Pg,我认为这是由diesel生成的模式引起的,因为我的代码中没有任何与postgres相关的代码,除了[=15] =], 我用 features=["postgres"].

进口柴油

下面是我的代码:

use diesel::select;
    
use crate::authenticator::AuthDB;
use crate::diesel::dsl::exists;
use crate::diesel::prelude::*;
use crate::schema::users;
    
pub struct DB<C, B>
where
    C: diesel::Connection<Backend = B>,
    B: diesel::backend::Backend,
{
    conn: C,
}
    
impl<C, B> AuthDB for DB<C, B>
where
    C: diesel::Connection<Backend = B>,
    B: diesel::backend::Backend + diesel::sql_types::HasSqlType<diesel::sql_types::Bool>,
{
    fn exists_by_id_and_password(
        &self,
        id: String,
        password: String,
    ) -> Result<bool, crate::error::Error> {
        Ok(select(exists(
            users::dsl::users.filter(
                users::dsl::username
                    .eq(id)
                    .and(users::dsl::password.eq(password)),
            ),
        ))
        .get_result::<bool>(&self.conn)?)
    }
}

我想知道,可以在没有具体后端类型的情况下实现特征

Rustc 确实已经作为错误消息的一部分发出了根本问题是什么:

required because of the requirements on the impl of `LoadQuery<C, bool>` for `diesel::query_builder::SelectStatement<(), query_builder::select_clause::SelectClause<Exists<diesel::query_builder::SelectStatement<table, query_builder::select_clause::DefaultSelectClause, query_builder::distinct_clause::NoDistinctClause, query_builder::where_clause::WhereClause<And<diesel::expression::operators::Eq<columns::username, diesel::expression::bound::Bound<diesel::sql_types::Text, std::string::String>>, diesel::expression::operators::Eq<columns::password, diesel::expression::bound::Bound<diesel::sql_types::Text, std::string::String>>>>>>>>`

这可以理解为 LoadQuery<C, bool> 需要为您的查询实现(long 类型代表您在编译时的查询)。

现在 rustc 尤其是错误报告部分真的很聪明。它知道可能存在的 impl 并尝试通过在此处建议“简单”解决方案来提供帮助。这就是为什么它建议 Backend 应该是 Pg 因为为此找到了该特征的唯一含义。这是唯一存在的实现,因为你只启用了一个后端(postgres 后端),我希望如果你通过 diesels 功能标志在 Cargo.toml 中添加更多后端,错误消息会改变。

那么如何以支持所有后端的方式编写该特征绑定。编译器已经用 required because … 行提示了方向。您需要声明 LoadQuery 已为您的查询实现。不幸的是,现在您不能只从错误消息中复制相应的类型,因为这不是 public API 中可用的类型。所以这似乎是一个死胡同。 The documentation lists LoadQuery 特征的两个潜在含义。我们对第二个感兴趣,因为我们的类型不是 SqlQuery。现在查看所需的边界,我们可以观察到以下内容:

  • Conn: Connection => 已经完成,
  • Conn::Backend: HasSqlType<T::SqlType> => 已经完成,
  • T: AsQuery + RunQueryDsl<Conn> => 这要求我们可以命名 T,这是我们的查询类型。那是不可能的,所以让我们暂时忽略它。
  • T::Query: QueryFragment<Conn::Backend> + QueryId => 同上次绑定,需要命名为T.
  • U: Queryable<T::SqlType, Conn::Backend> => 那个很有趣。

所以让我们详细看看`U: Queryable bound:

  • U 是您的查询返回的类型。所以在这种情况下 bool
  • T::SqlType 是查询返回的 sql 类型,因此是来自 diesel::sql_types 的类型。 exists returns 一个布尔值,所以 diesel::sql_type::Bool
  • Conn::Backend 是您的通用 B 类型。

这意味着我们需要验证bool: Queryable<diesels::sql_type::Bool, B>There is an existing impl in diesel which requires that bool: FromSql<diesel::sql_types::Bool, B>. Again there are existing impls 也是如此,但请注意,这些仅针对特定后端实现。所以这里发生的是 rustc 看到那个 impl 并得出结论,这是唯一可用的 impl。这意味着 B 必须是 Pg 否则这个函数将无效。现在有另一种方法可以让 rustc 相信这个 trait impl 存在,那就是将它添加为 trait bound。这为您提供了以下工作实现:

impl<C, B> AuthDB for DB<C, B>
where
    C: diesel::Connection<Backend = B>,
    B: diesel::backend::Backend + diesel::sql_types::HasSqlType<diesel::sql_types::Bool>,
    bool: diesel::deserialize::FromSql<diesel::sql_types::Bool, B>
{
    fn exists_by_id_and_password(
        &self,
        id: String,
        password: String,
    ) -> QueryResult<bool> {
        Ok(diesel::select(diesel::dsl::exists(
            users::dsl::users.filter(
                users::dsl::username
                    .eq(id)
                    .and(users::dsl::password.eq(password)),
            ),
        ))
        .get_result::<bool>(&self.conn)?)
    }
}


旁注:

如果启用额外的后端,我会收到以下错误消息

error[E0277]: the trait bound `bool: FromSql<Bool, B>` is not satisfied
  --> src/main.rs:39:10
   |
39 |         .get_result::<bool>(&self.conn)?)
   |          ^^^^^^^^^^ the trait `FromSql<Bool, B>` is not implemented for `bool`
   |
   = note: required because of the requirements on the impl of `Queryable<Bool, B>` for `bool`
   = note: required because of the requirements on the impl of `LoadQuery<C, bool>` for `diesel::query_builder::SelectStatement<(), query_builder::select_clause::SelectClause<Exists<diesel::query_builder::SelectStatement<table, query_builder::select_clause::DefaultSelectClause, query_builder::distinct_clause::NoDistinctClause, query_builder::where_clause::WhereClause<And<diesel::expression::operators::Eq<columns::username, diesel::expression::bound::Bound<diesel::sql_types::Text, std::string::String>>, diesel::expression::operators::Eq<columns::password, diesel::expression::bound::Bound<diesel::sql_types::Text, std::string::String>>>>>>>>`
help: consider extending the `where` bound, but there might be an alternative better way to express this requirement
   |
25 |     B: diesel::backend::Backend + diesel::sql_types::HasSqlType<diesel::sql_types::Bool>, bool: FromSql<Bool, B>
   |                                                                                         ~~~~~~~~~~~~~~~~~~~~~~~~

这恰好表明了上述特征界限。


另外两个半相关的旁注:

  • 没有理由明确地将后端作为 DB 结构的第二个参数。您可以随时通过 C::Backend.
  • 轻松访问它
  • 显然你不应该将密码作为明文存储在你的数据库中