0
votes

I am working on a larger private project and introduced something like a function registry for hooks to be used. That worked well so far, till I was forced to use a a type with a named lifetime In my case it was rusqlite::Transaction<'c>. Due to this all dependend strucs has to introduce a named lifetime too, right?

Finally I got some compiler lifetime errors, I dont know, how to resolve them.

To narrow down the problem I wrote this example code. Please note that you may not change the Transaction-Struct hence this is just a example for any struc require a named lifetime parameter.

use std::error::Error;

struct Holder<T> {
    item: fn(&T) -> (),
}

impl <T> Holder<T> {
    fn new(f: fn(&T) -> ()) -> Holder<T>{
        Holder{item: f}
    }

    fn exe(&self,i: &T){
        let f = self.item;
        f(i);
    }
}

struct Transaction<'c> {
    connection: &'c str,
}

impl <'c> Transaction<'c> {
    fn new(c: &'c str) -> Transaction {
        Transaction{connection: c}
    }
}

fn doSomething(t: &Transaction) {
    println!("I have done Something with {}",&t.connection);
}

pub fn main() -> Result<(), Box<dyn Error>> {

    //let f: fn(&Transaction) = |x| doSomething(x);
    let h: Holder<Transaction> = Holder::new(doSomething) ;
    {
        let connection = String::from("c1");
        let tran = Transaction::new(&connection);
        h.exe(&tran);
        h.exe(&tran);
        doSomething(&tran);
        doSomething(&tran);
    }
    {
        let connection = String::from("c2");
        let tran = Transaction::new(&connection);
        h.exe(&tran);
        h.exe(&tran);
        doSomething(&tran);
        doSomething(&tran);
    }

    Ok(())
}

If I use doSomething then it works but if I put doSomething within my generic holder I got lifetime errors like this:

error[E0597]: `tran` does not live long enough
  --> src\t.rs:39:15
   |
   |               ^^^^^ borrowed value does not live long enough
40 |         h.exe(&tran);
41 |     }
   |     - `tran` dropped here while still borrowed
...
45 |         h.exe(&tran);
   |         - borrow later used here

In the end, I could understand that the problem raise, hence the lifetime of the Transaction was bound to the Holder. But how to tell Rust, that this lifetime is a function argument and do not has to be bound to the Holder-Struct, so that the example is safe to work?

1

1 Answers

0
votes

After hours and hours of try, repeat, and try again I found a solution.

Replacing fn(&T) with T only and put in fn(&Transaction) instead of Transaction for T from the outside made my example finally work.

Of course I had to manually execute the function outside an impl. But this approach worked for my bigger project too.

It feels a little bit ugly, hence the behaviour fells inconsist at this point in Rust's type system to me.

Full Code:

use std::error::Error;

struct Holder<T> {
    item: T,
}

impl <T> Holder<T> {
    fn new(f: T) -> Holder<T>{
        Holder{item: f}
    }

    fn get(&self) -> &T {
        &self.item
    }
}


struct Transaction<'c> {
    connection: &'c String,
}

impl <'c> Transaction<'c> {
    fn new(c: &'c String) -> Transaction {
        Transaction{connection: c}
    }
}

fn doSomething(t: &Transaction) {
    println!("I have done Something with {}",&t.connection);
}

type RegF = fn(&Transaction) -> ();

pub fn main() -> Result<(), Box<dyn Error>> {

    let h: Holder<RegF> = Holder::new(doSomething);

    {
        let connection = String::from("c1");
        let tran = Transaction::new(&connection);
        let f = h.get();
        f(&tran);
        f(&tran);
        doSomething(&tran);
        doSomething(&tran);
    }
    {
        let connection = String::from("c2");
        let tran = Transaction::new(&connection);
        let f = h.get();
        f(&tran);
        f(&tran);
        doSomething(&tran);
        doSomething(&tran);
    }

    Ok(())
}