Implement new() in struct so as to not drop value after method ends

Hi, having some problems with the borrow checker as a newbie in rust! (Shocker to you all, I'm sure~)
I understand lifetime to some degree, but not all the way as I'm sure will become apparent.

So I'm trying to write a wrapper around this sqlite crate but can't quite get the lifetime right, nor do I know what is wrong. Otherwise, I would've tried to make a minimal sample here. Anyway, this is what I've got:

use sqlite::{Connection, Statement};

static DB_FILE_NAME: &str = "./db.sqlite";
const TABLE_NAME: &str = "example_table";
const COLUMN_NAME: &str = "example_column";

pub struct SQLiteWrapper<'a> {
    connection: &'a Connection,
    statement: Statement<'a>,

}

impl<'a> SQLiteWrapper<'a> {
    fn new() -> SQLiteWrapper<'a> {
        let conn: &'a Connection = &sqlite::open(&DB_FILE_NAME).unwrap();
        conn.execute(format!(
            "CREATE TABLE {table_name} ({column_name} TEXT);",
            table_name = TABLE_NAME,
            column_name = COLUMN_NAME,
        ))
        .unwrap_or_else(|err| println!("{:?}", err));

        let statement = conn
            .prepare(format!(
                "INSERT INTO {table_name} VALUES ('?');",
                table_name = COLUMN_NAME,
            ))
            .unwrap();

        SQLiteWrapper {
            connection: conn,
            statement,
        }
    }

    fn insert_data(&mut self){
        self.statement.bind(1, "some text").unwrap();
    }
}

fn main() -> std::io::Result<()> {
    SQLiteWrapper::new();
    Ok(())
}

And I'm getting these errors:

error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:14:37
   |
12 | impl<'a> SQLiteWrapper<'a> {
   |      -- lifetime `'a` defined here
13 |     fn new() -> SQLiteWrapper<'a> {
14 |         let conn: &'a Connection = &sqlite::open(&DB_FILE_NAME).unwrap();
   |                   --------------    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ creates a temporary which is freed while still in use
   |                   |
   |                   type annotation requires that borrow lasts for `'a`
...
34 |     }
   |     - temporary value is freed at the end of this statement
error: aborting due to previous error
For more information about this error, try `rustc --explain E0716`.

Should I even be doing this in Rust? I know some OOP designs don't translate well unless you use a crate.

Either way, correct me if I'm wrong but I feel like this should be doable. I don't really understand what the problem is more than that the Connection struct seems to be freed instead of following 'a lifetime.

Is it because of how Connection::open() is implemented? Its signature is this:

pub fn open<T: AsRef<Path>>(path: T) -> Result<Connection> {

And from reading a few things (such as this blog) it seems as if though AsRef might be what is confusing me.

If anyone could help explain or point to what I need to look up that I'm not getting here, that'd be awesome!

You want SQLiteWrapper to have ownership of the connection, so you should not be using a reference.

2 Likes

Your problem is here:

You say that SQLiteWrapper refers to some Connection, but you never pass in a connection via new. So what connection are you referring to? One that you put on the stack when you call new, and which will be deallocated before new returns? That's not going to work. You have to take ownership of the connection, or pass it into new.

1 Like

Thanks for replying!

I did try that at some point. I'm assuming these are the changes I should make to have the connection be owned by SQLiteWrapper:

pub struct SQLiteWrapper<'a> {
    connection: Connection,
    statement: Statement<'a>,
}

impl<'a> SQLiteWrapper<'a> {
    fn new() -> SQLiteWrapper<'a> {
        let conn: Connection = sqlite::open(&DB_FILE_NAME).unwrap();
        conn.execute(format!(
            "CREATE TABLE {table_name} ({column_name} TEXT);",
            table_name = TABLE_NAME,
            column_name = COLUMN_NAME,
        ))
        .unwrap_or_else(|err| println!("{:?}", err));
        let statement = conn
            .prepare(format!(
                "INSERT INTO {table_name} VALUES ('?');",
                table_name = COLUMN_NAME,
            ))
            .unwrap();
        SQLiteWrapper {
            connection: conn,
            statement,
        }
    }

    fn insert_data(&mut self) {
        self.statement.bind(1, "some text").unwrap();
    }
}

However, I get this error instead then:

error[E0515]: cannot return value referencing local variable `conn`
  --> src/main.rs:30:9
   |
23 |           let statement = conn
   |                           ---- `conn` is borrowed here
...
30 | /         SQLiteWrapper {
31 | |             connection: conn,
32 | |             statement,
33 | |         }
   | |_________^ returns a value referencing data owned by the current function
error[E0505]: cannot move out of `conn` because it is borrowed
  --> src/main.rs:31:25
   |
12 |   impl<'a> SQLiteWrapper<'a> {
   |        -- lifetime `'a` defined here
...
23 |           let statement = conn
   |                           ---- borrow of `conn` occurs here
...
30 | /         SQLiteWrapper {
31 | |             connection: conn,
   | |                         ^^^^ move out of `conn` occurs here
32 | |             statement,
33 | |         }
   | |_________- returning this value requires that `conn` is borrowed for `'a`
error: aborting due to 2 previous errors
Some errors have detailed explanations: E0505, E0515.
For more information about an error, try `rustc --explain E0505`.

I suppose I figured that if I borrowed both for lifetime 'a and 'a was the lifetime of the struct, the conn and statement would get cleaned up once the SQLiteWrapper struct goes out of scope somewhere in the caller of SQLiteWrapper::new().

Sorry, Statement contains a reference to Connection, so you're really trying to build a self-referential struct, which cannot be done. As for 'a, it is a generic parameter, and is thus chosen by the user, and can therefore easily outlive the struct, making the reference inside your statement not living long enough.

2 Likes

Right, that makes more sense. I felt like I was going in circles! It isn't even possible to do by allocating the statement to heap as a solution either, is it? Guessing no, since it'd still be cyclic.

Do you have any suggestions or sources on how to keep code clean in Rust? I'm not sure how to abstract these things away really. Sorta feels like learning to program anew all over again.

Either way, thank you Alice. And you too skysch!

I would create the Connection outside of the SQLiteWrapper and pass it into the constructor.

fn new(conn: &'a Connection) -> SQLiteWrapper<'a> { ... }
1 Like

Stuff like the heap is irrelevant here, since the issue is one of ownership. You need to make the connection owned in a separate variable from where the statement is owned.

3 Likes

That seems like the closest option, yeah!

Just curious, how did you determine it was self-referential? I'm guessing it has something to do with this function, and using self.raw when creating the Statement struct. But I'm not entirely sure.

    pub fn prepare<'l, T: AsRef<str>>(&'l self, statement: T) -> Result<Statement<'l>> {
        ::statement::new(self.raw, statement)
    }

Again, thank you guys for helping me out. I think that clears up my confusion a bit.

I determined it contained a reference to something outside itself by noticing the lifetime in its type, since that's what having such a lifetime means, and given the only thing you gave it access to was the connection and a static string slice, it had to be the connection.

Exactly how this reference is stored internally is not of importance. It is how the lifetimes are laid out in the type system that determine whether you will run into issues with self-referential types ­— even if it did not actually contain any references and just had the lifetime parameter, you would still be in trouble for the same reasons.

This is also exposes a different way to see that your type is self-referential. It has a lifetime parameter, which means it must borrow from some value stored outside of itself, but it does not. Instead the borrow is inside itself, thus the issue.

4 Likes

Aa, right, that makes sense. I hope I'll get an intuition or understand it fully eventually. But I think I've got some more tools on how to think about it now!

(Also, whoops, thought I could select multiple replies as having solved the topic question.. Sorry if you got a bit spammed there.)

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.