How to move together a value and another value that reference it

Hi,

I'm trying to create a custom iterator for a query execution using rusqlite.
The final goal of this iterator is to return arrow::RecordBach instances containing at most 100 rows.

The naive implementation below does not compile. I understand why the borrow checker is not happy but there should be a way to go around the borrow checker since the SqliteQuery returned could guaranty the lifetime of the values involved.

My Understanding is that the raw_query() method by taking a mutable reference of the statement prevent me to later move the statement into the SqliteQuery.

  pub fn raw_query(&mut self) -> Rows<'_> 

I tried to play with RefCell for a runtime checking of the borrowing but apparently I'm still not yet experimented in RUST to play with this...

Any help would be greatly appreciated!

type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;

struct RecordBatch {/* simulate an arrow_array::RecordBatch */}

struct SqliteQuery<'c> {
    stmt: rusqlite::Statement<'c>,
    rows: rusqlite::Rows<'c>,
}

// Implementation of the custom iterator that returns RecordBatch instances each containing at most 100 rows 
impl<'c> Iterator for SqliteQuery<'c> {
    type Item = Result<RecordBatch>;

    fn next(&mut self) -> Option<Result<RecordBatch>> {
        let batch = RecordBatch {};
        for _i in 0..100 {
            match self.rows.next() {
                Ok(Some(_row)) => {
                    todo!("Add row to batch")
                }
                Ok(None) => {
                    return None;
                }
                Err(e) => {
                    return Some(Err(e.into()));
                }
            }
        }
        Some(Ok(batch))
    }
}

// Function to query the database and return the custom iterator
fn query<'c>(
    conn: &'c rusqlite::Connection,
    sql: String,
) -> Result<Box<dyn Iterator<Item = Result<RecordBatch>> + 'c>> {
    let mut stmt: rusqlite::Statement = conn.prepare(sql.as_str())?;
    let rows: rusqlite::Rows = stmt.raw_query();
    let query = SqliteQuery { stmt, rows };
    Ok(Box::new(query))
}

fn main() {
    let conn = rusqlite::Connection::open_in_memory().unwrap();
    let rows = query(&conn, "SELECT 1".to_string()).unwrap();
    for row in rows {
        match row {
            Ok(_batch) => {
                // Do something with the record batch
            }
            Err(e) => {
                eprintln!("Error: {}", e);
            }
        }
    }
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0505]: cannot move out of `stmt` because it is borrowed
  --> src/lib.rs:40:31
   |
34 | fn query<'c>(
   |          -- lifetime `'c` defined here
...
38 |     let mut stmt: rusqlite::Statement = conn.prepare(sql.as_str())?;
   |         -------- binding `stmt` declared here
39 |     let rows: rusqlite::Rows = stmt.raw_query();
   |                                ---- borrow of `stmt` occurs here
40 |     let query = SqliteQuery { stmt, rows };
   |                               ^^^^ move out of `stmt` occurs here
41 |     Ok(Box::new(query))
   |     ------------------- returning this value requires that `stmt` is borrowed for `'c`

error[E0515]: cannot return value referencing local variable `stmt`
  --> src/lib.rs:41:5
   |
39 |     let rows: rusqlite::Rows = stmt.raw_query();
   |                                ---- `stmt` is borrowed here
40 |     let query = SqliteQuery { stmt, rows };
41 |     Ok(Box::new(query))
   |     ^^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function

Some errors have detailed explanations: E0505, E0515.
For more information about an error, try `rustc --explain E0505`.
error: could not compile `playground` (lib) due to 2 previous errors

That would be a self-referencial struct, so without a lot of unsafe that is extremely hard to get correct, you can't do this.

There are some crates like ouroboros which try to tackle this pattern. They may or may not be sound. (All such crates I'm aware of have had soundness issues in the past, if not still known problems in the present.)

Note also that you have two layers of borrowing,[1] which may present more challenges.

You may be thinking of "lifetimes" as "how long a value is around" (liveness scope of a value), since that what it means in some other languages. But Rust lifetimes (those '_ things) are generally about the duration of some borrow, which is not the same thing.

Borrowed values cannot be moved, and your entire goal is to move (return) your query.


Side notes...

I'm not intimately familiar with the crate, but it looks like query_map is related to your iteration goals.

And by the way, this code snippet:

    fn next(&mut self) -> Option<Result<RecordBatch>> {
        let batch = RecordBatch {};
        for _i in 0..100 {

doesn't do what you intended, because the loop starts from 0 every time next gets called.


  1. stmt borrows conn and rows borrows stmt ↩ī¸Ž

You could use a visitor pattern instead.

1 Like

As far as I know, maybe Pin can help (I havn't coded with it however).

Not in any actionable way without also using unsafe. Perhaps also very hard or impossible to get right with anything involving lifetimes you don't control, as in this case.

The popular dedicated crates are probably the best bet if one wants to pursue self-referential types, despite any potential unsoundness. At least they're somewhat battle tested.

Thanks for taking the time to look at this. Unfortunately, the visitor pattern would not work for my usage, I need the consumer of the iterator to be in control of when the next batch of record should be loaded.

You could use a macro, though you'll probably be wishing you had super let.

No, Pin is not capable of creating self-referential structs. It acts only as a type-system-level warning that its content may be very sensitive to the address it's at, due to reasons that are beyond Pin's capability to explain, or make safe.

1 Like

Yes, you're right. I didn't have much experience with Pin. However, I saw many use Pin with pin-project crate to handle self-referencial struct, and just guess it may work.

pin-project lets you create wrappers — create a struct that contains another type that needs to be pinned — but it doesn't let you create an actual Pin-based-self-reference that didn't exist in the wrapped type already.

There's currently no tool allowing you to do that in safe code, as far as I know. This is partly due to the trouble over the precise definition of how to actually write correct code that uses such a self-reference without tripping over the basic &mut no-aliasing rule; this open question has been resolved through RFC 3467, but the solution described by that RFC is not yet actually implemented.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.