首页 > 解决方案 > 编写一个特征来使用多个数据库后端给了我“错误[E0597]:`connection`的寿命不够长”

问题描述

我的程序需要能够从多个数据库(Postgres 和 Oracle)中读取数据。

原创尝试

所以我想我会使用特征来隐藏实现细节和通用函数来获取数据。Transaction可悲的是,对于 Postgres 后端,我需要一个作弊功能:

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和相关类型

在 Ö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.

标签: rusttraitslifetime

解决方案


Nothing good ever comes from putting lifetime on &'a mut self.

When you find yourself doing this, you've dug to deep in the wrong place.

  • &mut isn't just mutable, it means exclusive access. And for safety this exclusivity applies to everything derived from it.

  • With shared borrows (&) the compiler is pretty flexible and can shorten the lifetimes in places where this is needed, so you can sprinkle 'a all over the place and it'll work out. But for obscure safety reasons &mut lifetimes have to be invariant, i.e. totally inflexible. Can't be shortened, can't be extended. &mut self.transaction is then called a re-borrow, and it creates a new borrow with a new lifetime.

When you have a lifetime on a trait DataSource<'a>, and &mut makes this lifetime invariant, it ends up meaning that everything 'a touches had to exist already before the object implementing DataSource has been created. And when you mix it with generate<'a> it expands that scope to mean everything with that lifetime had to be created even before genreate has been called.

I can't run the code, so I'm not sure if that's enough, but you've probably meant:

fn transaction<'tmp>(&'tmp mut self) -> &'tmp mut postgres::Transaction<'tmp>

simpler written as: (with no <'a> on DataSource)

fn transaction(&mut self) -> &mut postgres::Transaction<'_>

which should cast lifetime of Transaction to a scope started with that method call, which is as good as you can get anyway because of &mut in the return type.


推荐阅读