Saya menggunakan perpustakaan rustqlite untuk database SQLite (taman bermain)

use std::ops::Deref;

pub struct Connection {
}

impl Connection {
  pub fn transaction(&mut self) -> Transaction {
    Transaction::new(self)
  }
}

pub struct Transaction<'conn> {
    conn: &'conn Connection
}

impl Transaction<'_> {
    pub fn new(conn: &mut Connection) -> Transaction {
        Transaction{ conn }
    }
    
    pub fn commit(mut self) -> Result<(), ()> {
        Ok(())
    }
}

impl Deref for Transaction<'_> {
    type Target = Connection;

    #[inline]
    fn deref(&self) -> &Connection {
        self.conn
    }
}

Dengan implementasi ini, objek Transaction akan mengambil kepemilikan objek Connection. Pada saat yang sama, ia juga mengimplementasikan sifat Deref sehingga kita dapat memanggil semua metode dari struct Transaction seperti dari struct Connection.

Detail implementasi di sini

Dari kode aplikasi saya, saya ingin memiliki satu objek yang dapat diwakili oleh Transaction atau Connection. Hal ini diperlukan karena logika memiliki flag untuk memutuskan menggunakan transaksi atau tidak. Ada pemeran untuk memperlakukan objek Transaction sebagai objek Connection:

let conn = create_connection(); // Connection
let tx = conn.transaction(); // Transaction
let conn: &Transaction = &tx; // cast back to Connection type from the Transaction type

Namun, saya tidak tahu bagaimana mengatur kode ini dari POV aplikasi dengan kondisi. Ini pseudocode saya:

pub fn execute(is_tx: bool) {
    // conn will have Connection type
    let conn = match is_tx {
        true => &create_connection(),
        false => {
            let x = create_connection().transaction();
            let t: &Connection = &x;
            t
        }
    };

    // do other things with conn object
}

pub fn create_connection() -> Connection {
    Connection{}
}

Namun, akan ada kesalahan

error[E0716]: temporary value dropped while borrowed
  --> src/lib.rs:36:21
   |
36 |             let x = create_connection().transaction();
   |                     ^^^^^^^^^^^^^^^^^^^              - temporary value is freed at the end of this statement
   |                     |
   |                     creates a temporary which is freed while still in use
37 |             let t: &Connection = &x;
   |                                  -- borrow later used here
   |
   = note: consider using a `let` binding to create a longer lived value

error[E0597]: `x` does not live long enough
  --> src/lib.rs:37:34
   |
37 |             let t: &Connection = &x;
   |                                  ^^ borrowed value does not live long enough
38 |             t
   |             - borrow later used here
39 |         }
   |         - `x` dropped here while still borrowed

Saya memahami kesalahannya, tetapi saya telah mencoba beberapa solusi tanpa hasil, terutama karena struct Transaction mengambil alih kepemilikan struct Connection. Bagaimana saya bisa memperbaiki ini?

1
Trần Kim Dự 12 Mei 2021, 20:19

1 menjawab

Jawaban Terbaik

Penafian: Saya belum mencoba kode ini. Tapi itu hanya di sini untuk memberi Anda gambaran tentang arah untuk masuk.

Pertama, variabel lokal (pada tumpukan) di Rust harus berukuran tetap. Ini adalah salah satu masalah yang Anda hadapi. Transaksi dan Koneksi tidak berukuran sama. Jadi Anda tidak dapat mencapai "Polimorfisme" di tumpukan, tanpa beberapa trik.

Dua cara untuk melakukan ini adalah tipe Enum, dan Tinju (menempatkan struct di Heap, dan menambahkan VTable).

Saya tidak akan membahas Tinju, karena itu relatif sederhana.

Masalah kedua yang Anda miliki adalah bahwa masa pakai Transaction terkait dengan Connection, jadi setiap perpindahan Transaction akan mengharuskan Anda untuk move Connection demikian juga.

enum MyConnection<'a> {
   TransactionConnection {
       transaction: Transaction<'a>
    },
   NakedConnection{
    connection: Connection
   }
}

impl MyConnection<'a> {
    fn commit(mut &self) -> Result<()> {
        match self {
           MyConnection::NakedConnection =>
              Ok(()),
           MyConnection::TransactionConnection { transaction } =>
              transaction.commit()
        }
    }
}

impl<'a> Deref for MyConnection<'a>
{
    type Target = Connection;
    #[inline]
    fn deref(&self) -> &Connection {
        match self {
            MyConnection::TransactionConnection { transaction } =>
                transaction.conn,
            MyConnection::NakedConnection { connection } =>
                connection,
        }
    }
}

Enum dan Deref ini akan memungkinkan Anda untuk memegang struct yang dapat mengakses koneksi.

Ini adalah bagaimana Anda menggunakan kode di atas.

pub fn execute(is_tx: bool) {
    // conn will have Connection type
    let mut conn = create_connection();
    let conn = match is_tx {
        false => {
            MyConnection::NakedConnection { connection: conn }
        },
        true => {
            let trans = conn.transaction();
            MyConnection::TransactionConnection {                
                transaction: trans,
            }
        }
    };
    conn.do_stuff();
    conn.commit();
}

Perhatikan bahwa create_connection telah dipindahkan ke luar pertandingan. Ini agar cakupan koneksi akan selalu lebih besar dari cakupan 'a dari MyConnection. Ini "memecahkan" masalah kedua.

2
Aron 13 Mei 2021, 04:56