我的程序需要能够从多个数据库(Postgres和Oracle(读取其数据。
原始尝试
所以我想我会使用特征来隐藏实现细节和一个通用函数来获取数据。可悲的是,在Postgres后端的情况下,我需要一个作弊功能来获得Transaction
:
trait DataSource<'a> {
fn get_data(&mut self) -> String;
fn transaction(&mut self) -> &mut postgres::Transaction<'a> {
unimplemented!()
}
}
trait BackendConnection<'a, TS>
where
TS: DataSource<'a>,
{
fn data_source(&'a mut self) -> TS;
}
trait BackendConfiguration<'a, TC, TS>
where
TC: BackendConnection<'a, TS>,
TS: DataSource<'a>,
{
fn connect(&self) -> TC;
}
fn generate<'a, TF, TC, TS>(config: &TF)
where
TF: BackendConfiguration<'a, TC, TS>,
TC: BackendConnection<'a, TS> + 'a,
TS: DataSource<'a> + 'a,
{
let mut connection = config.connect();
let mut source = connection.data_source();
println!("{:?}", source.get_data());
}
// You can ignore all this, it is there just to show the reason why the lifetime is needed in `data_source(&'a mut self)` above.
mod pg {
pub struct PgSource<'a> {transaction: postgres::Transaction<'a>}
impl<'a> super::DataSource<'a> for PgSource<'a> {
fn get_data(&mut self) -> String {
let mut data = String::new();
for row in self.transaction.query("SELECT CURRENT_TIMESTAMP", &[]).unwrap() {
let st: std::time::SystemTime = row.get(0);
data.push_str(&format!("{:?}n", st));
}
data
}
fn transaction(&mut self) -> &mut postgres::Transaction<'a> {
&mut self.transaction
}
}
pub struct PgConnection {client: postgres::Client}
impl<'a> super::BackendConnection<'a, PgSource<'a>> for PgConnection {
fn data_source(&'a mut self) -> PgSource<'a> {
let transaction = self.client.transaction().unwrap();
PgSource { transaction }
}
}
pub struct PgConfiguration {config: postgres::Config}
impl PgConfiguration {
pub fn new(params: &str) -> Self {
let config = params.parse::<postgres::Config>().unwrap();
Self { config }
}
}
impl<'a> super::BackendConfiguration<'a, PgConnection, PgSource<'a>> for PgConfiguration {
fn connect(&self) -> PgConnection {
let client = self.config.connect(postgres::tls::NoTls).unwrap();
PgConnection { client }
}
}
}
但是 Rust 编译器不接受这一点:
error[E0597]: `connection` does not live long enough
--> src/lib.rs:22:22
|
17 | fn generate<'a, TF, TC, TS>(config: &TF)
| -- lifetime `'a` defined here
...
22 | let mut source = connection.data_source();
| ^^^^^^^^^^--------------
| |
| borrowed value does not live long enough
| argument requires that `connection` is borrowed for `'a`
23 | println!("{:?}", source.get_data());
24 | }
| - `connection` dropped here while still borrowed
我该如何描述connection
source
超长寿?我尝试在source
周围引入一个范围或connection
'b: 'a
并没有产生积极的结果。
再次尝试使用Box
和相关类型
在Ömer Erden和Kornel的一些评论之后,我尝试对特征进行装箱并使用相关类型。呜,它编译了!
#![feature(generic_associated_types)]
trait DataSource {
fn get_data(&mut self) -> String;
fn transaction(&mut self) -> postgres::Transaction<'_> { unimplemented!() }
}
trait BackendConnection {
type Source<'a>;
fn data_source(&mut self) -> Self::Source<'_>;
}
trait BackendConfiguration {
type Connection;
fn connect(&self) -> Self::Connection;
}
fn generate<TF>(config: &TF)
where
TF: BackendConfiguration<Connection=Box<dyn BackendConnection<Source=Box<dyn DataSource>>>>
{
let mut connection = config.connect();
let mut source = connection.data_source();
println!("{:?}", source.get_data());
}
// You can ignore all this, it is there just to show the reason why
// the lifetime is needed in `data_source(&'a mut self)` above.
mod pg {
pub struct PgSource<'a> {transaction: postgres::Transaction<'a>}
impl super::DataSource for PgSource<'_> {
fn get_data(&mut self) -> String {
let mut data = String::new();
for row in self.transaction.query("SELECT CURRENT_TIMESTAMP", &[]).unwrap() {
let st: std::time::SystemTime = row.get(0);
data.push_str(&format!("{:?}n", st));
}
data
}
fn transaction(&mut self) -> postgres::Transaction<'_> {
self.transaction.transaction().unwrap()
}
}
pub struct PgConnection {client: postgres::Client}
impl super::BackendConnection for PgConnection {
type Source<'a> = Box<PgSource<'a>>;
fn data_source(&mut self) -> Self::Source<'_> {
let transaction = self.client.transaction().unwrap();
Box::new(PgSource { transaction })
}
}
pub struct PgConfiguration {config: postgres::Config}
impl PgConfiguration {
pub fn new(params: &str) -> Self {
let config = params.parse::<postgres::Config>().unwrap();
Self { config }
}
}
impl super::BackendConfiguration for PgConfiguration {
type Connection = Box<PgConnection>;
fn connect(&self) -> Self::Connection {
let client = self.config.connect(postgres::tls::NoTls).unwrap();
Box::new(PgConnection { client })
}
}
}
但是当我使用泛型时它仍然无法编译:
fn main() {
let cfg = pg::PgConfiguration::new("host=host.example user=myself");
generate(&cfg);
}
错误是:
error[E0271]: type mismatch resolving `<pg::PgConfiguration as BackendConfiguration>::Connection == std::boxed::Box<(dyn BackendConnection<Source = std::boxed::Box<(dyn DataSource + 'static)>> + 'static)>`
--> src/lib.rs:26:5
|
15 | fn generate<TF>(config: &TF)
| --------
16 | where
17 | TF: BackendConfiguration<Connection=Box<dyn BackendConnection<Source=Box<dyn DataSource>>>>
| ----------------------------------------------------------------- required by this bound in `generate`
...
26 | generate(&cfg);
| ^^^^^^^^ expected trait object `dyn BackendConnection`, found struct `pg::PgConnection`
|
= note: expected struct `std::boxed::Box<(dyn BackendConnection<Source = std::boxed::Box<(dyn DataSource + 'static)>> + 'static)>`
found struct `std::boxed::Box<pg::PgConnection>`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0271`.
注意
如果我手动单态化generate
它可以工作:
fn generate_for_pg(config: &pg::PgConfiguration) {
let mut connection = config.connect();
let mut source = connection.data_source();
println!("{:?}", source.get_data());
}
但是我当然想避免这种情况,因为它会产生代码重复(我必须编写一个generate_for_oracle
(。
把一生放在&'a mut self
上不会有什么好处。
当你发现自己这样做时,你已经在错误的地方挖得很深。
-
&mut
不仅是可变的,它还意味着独占访问权限。为了安全起见,这种排他性适用于由此衍生的一切。 -
使用共享借用(
&
(,编译器非常灵活,可以缩短需要的地方的生命周期,因此您可以将'a
洒满所有地方,它会成功。但出于模糊的安全原因&mut
寿命必须是不变的,即完全不灵活。不能缩短,不能延长。 然后&mut self.transaction
被称为再借款,它创造了具有新寿命的新借款。
当你在特质DataSource<'a>
上有一个生命周期,并且&mut
使这个生命周期不变时,它最终意味着在创建实现DataSource
的对象之前,'a
接触的所有内容都必须已经存在。当你把它和generate<'a>
混合时,它扩大了这个范围,意味着在调用genreate
之前,必须创建具有该生命周期的所有内容。
我无法运行代码,所以我不确定这是否足够,但您可能的意思是:
fn transaction<'tmp>(&'tmp mut self) -> &'tmp mut postgres::Transaction<'tmp>
更简单的写成:(DataSource
没有<'a>
(
fn transaction(&mut self) -> &mut postgres::Transaction<'_>
它应该将Transaction
的生存期强制转换为以该方法调用开始的范围,由于返回类型中的&mut
,无论如何都可以获得。
通过使用泛型关联类型,您可以将泛型类型限制为特征,因此可以避免将所有内容包装在框中,并让编译器自己查找类型:
#![feature(generic_associated_types)]
trait DataSource {
type Transaction;
fn get_data(&mut self) -> String;
fn transaction(&mut self) -> &mut Self::Transaction;
}
trait BackendConnection {
type Source<'a>: DataSource
where
Self: 'a;
fn data_source(&mut self) -> Self::Source<'_>;
}
trait BackendConfiguration {
type Connection: BackendConnection;
fn connect(&self) -> Self::Connection;
}
fn generate<TF>(config: &TF)
where
TF: BackendConfiguration
{
let mut connection = config.connect();
let mut source = connection.data_source();
println!("{:?}", source.get_data());
}
// You can ignore all this, it is there just to show the reason why the lifetime is needed in `data_source(&'a mut self)` above.
mod pg {
pub struct PgSource<'a> {
transaction: postgres::Transaction<'a>,
}
impl<'a> super::DataSource for PgSource<'a> {
type Transaction = postgres::Transaction<'a>;
fn get_data(&mut self) -> String {
let mut data = String::new();
for row in self
.transaction
.query("SELECT CURRENT_TIMESTAMP", &[])
.unwrap()
{
let st: std::time::SystemTime = row.get(0);
data.push_str(&format!("{:?}n", st));
}
data
}
fn transaction(&mut self) -> &mut Self::Transaction {
&mut self.transaction
}
}
pub struct PgConnection {
client: postgres::Client,
}
impl super::BackendConnection for PgConnection {
type Source<'a> = PgSource<'a>;
fn data_source(&mut self) -> PgSource<'_> {
let transaction = self.client.transaction().unwrap();
PgSource { transaction }
}
}
pub struct PgConfiguration {
config: postgres::Config,
}
impl PgConfiguration {
pub fn new(params: &str) -> Self {
let config = params.parse::<postgres::Config>().unwrap();
Self { config }
}
}
impl super::BackendConfiguration for PgConfiguration {
type Connection = PgConnection;
fn connect(&self) -> PgConnection {
let client = self.config.connect(postgres::tls::NoTls).unwrap();
PgConnection { client }
}
}
}
fn main() {
let cfg = pg::PgConfiguration::new("host=host.example user=myself");
generate(&cfg);
}
在这里用假类型调用生成函数没有问题
我设法使用以下特征使其工作:
(请注意,这仍然使用tokio-postgres中的某些类型,但这是朝着正确方向迈出的一步(
#[derive(Clone, PartialEq, Debug)]
pub struct Config {
pub name: String,
pub user: String,
pub host: String,
}
#[async_trait]
pub trait Database: Sync + Send {
type Transaction: Transaction;
type Connection: Connection<Transaction = Self::Transaction>;
async fn setup(&mut self, config: Config) -> Result<(), Error>;
async fn connect(&mut self) -> Result<Self::Connection, Error>;
}
#[async_trait]
pub trait Connection: Sync + Send {
type Transaction: Transaction;
async fn transaction(&mut self) -> Result<Self::Transaction, Error>;
}
#[async_trait]
pub trait Transaction: Sync + Send {
async fn query(
&self,
statement: &str,
params: &[&(dyn ToSql + Sync)],
) -> Result<Vec<Row>, Error>;
async fn query_one(
&self,
statement: &str,
params: &[&(dyn ToSql + Sync)],
) -> Result<Row, Error>;
async fn query_opt(
&self,
statement: &str,
params: &[&(dyn ToSql + Sync)],
) -> Result<Option<Row>, Error>;
async fn commit(self) -> Result<(), Error>;
async fn rollback(self) -> Result<(), Error>;
}
这可以像这样使用:
struct Repository<D, T, C>
where
T: Transaction,
C: Connection<Transaction = T>,
D: Database<Transaction = T, Connection = C>,
{
db: D,
}
#[async_trait]
trait UserRepo {
async fn get_user(&mut self, txn: &dyn Transaction) -> Result<(), Error>;
}
#[async_trait]
impl<D, T, C> UserRepo for Repository<D, T, C>
where
T: Transaction,
C: Connection<Transaction = T>,
D: Database<Transaction = T, Connection = C>,
{
async fn get_user(&mut self, txn: &dyn Transaction) -> Result<(), Error> {
unimplemented!()
}
}
使用UserRepo
特征时,所有这些泛型类型参数都会消失:
struct MyStruct {
repo: Box<dyn UserRepo>,
}
我在这里创建了一个代码审查堆栈交换问题,以获取有关简化这些特征定义的建议。
但它有效!希望这有帮助。