我的程序必须能够从多个数据库(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 }
}
}
}
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
或'b: 'a
的connection
周围引入范围,但没有得到积极的结果。
Box
和相关类型[ÖmerErden和Kornel发表评论后,我尝试将特征装箱并使用关联的类型。 Woohoow, it compiles!:
#![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`.
注意
如果我monomorphize generate
by hand it works:
generate
但是,我当然想避免这种情况,因为它会创建代码重复(我必须写
fn generate_for_pg(config: &pg::PgConfiguration) { let mut connection = config.connect(); let mut source = connection.data_source(); println!("{:?}", source.get_data()); }
)。
我的程序必须能够从多个数据库(Postgres和Oracle)中读取其数据。原始尝试因此,我认为我将使用特征来隐藏实现细节,并使用通用函数来获取...
[将生命周期放在generate_for_oracle
上永远不会有什么好处。