无法在锈柴油中使用通用参数实现特征
Posted
技术标签:
【中文标题】无法在锈柴油中使用通用参数实现特征【英文标题】:Cannot implement trait with generic param in rust diesel 【发布时间】:2022-01-05 23:13:09 【问题描述】:我定义了一个名为 exists_by_id_and_password
的特征。我不想用具体的数据库后端来实现它,所以在DB
struct 中添加了一些通用参数。但是编译器报错:
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
提到的错误,我认为这是由柴油生成的架构引起的,因为我的代码中没有任何与postgres相关的代码,除了cargo.toml
,我导入柴油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)?)
我想知道,可以在没有具体后端类型的情况下实现 trait
【问题讨论】:
【参考方案1】: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
因为为此找到了该特征的唯一 impl。这是唯一现有的 impl,因为您只启用了一个后端(postgres 后端),我希望如果您通过柴油功能标志在 Cargo.toml
添加更多后端,错误消息会发生变化。
那么如何以支持所有后端的方式编写该特征绑定。编译器已经用required because …
行暗示了一个方向。您需要声明为您的查询实现了LoadQuery
。现在不幸的是,您不能只从错误消息中复制相应的类型,因为这不是公共 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<:sqltype conn::backend> bound:
U
是您的查询返回的类型。所以bool
在这种情况下。
T::SqlType
是查询返回的sql类型,所以是来自diesel::sql_types
的类型。 exists
返回一个布尔值,所以 diesel::sql_type::Bool
Conn::Backend
是您的通用 B
类型。
这意味着我们需要验证bool: Queryable<diesels::sql_type::Bool, B>
。 There is an existing impl in diesel 需要 bool: FromSql<diesel::sql_types::Bool, B>
。同样也有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>
| ~~~~~~~~~~~~~~~~~~~~~~~~
这正好暗示了上面描述的 trait bound。
另外两个半相关的旁注:
没有理由明确地将后端作为DB
结构的第二个参数。您可以随时通过C::Backend
访问它。
显然,您不应将密码以明文形式存储在数据库中
【讨论】:
非常感谢您的回答。我读了两遍,确保自己了解它的每一个细节。我想我得到了通用的关键点,它允许我们将抽象层添加到另一个抽象层而不具体化基础抽象。但有时我们应该明确地具体说明一些关系以使整体是自洽的。对吗?以上是关于无法在锈柴油中使用通用参数实现特征的主要内容,如果未能解决你的问题,请参考以下文章