写特征以使用多个数据库后端给我“错误[E0597]:`connection`的生存时间不够长”]]

问题描述 投票:0回答:1

我的程序必须能够从多个数据库(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 compiler does not accept this

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: 'aconnection周围引入范围,但没有得到积极的结果。

再次尝试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 })
        }
    }
}

但还是fails to compile when I use the generic

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)中读取其数据。原始尝试因此,我认为我将使用特征来隐藏实现细节,并使用通用函数来获取...

rust traits lifetime
1个回答
0
投票

[将生命周期放在generate_for_oracle上永远不会有什么好处。

© www.soinside.com 2019 - 2024. All rights reserved.