Моя программа должна иметь возможность считывать свои данные из нескольких баз данных (Postgres и Oracle).
Оригинальная попытка
Поэтому я подумал, что буду использовать черты, чтобы скрыть реализации детали и обобщенную c функцию для получения данных. К сожалению, мне нужна чит-функция, чтобы получить Transaction
в случае Postgres backend:
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
или 'b: 'a
для connection
не дали положительных результатов.
Еще одна попытка с Box
и связанными типами
После некоторых комментариев Омера Эрден и Корнел Я попробовал боксировать черты и используя связанные типы. Woohoow, он компилируется! :
#![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 })
}
}
}
Но все равно не компилируется, когда я использую generi c:
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
).