[newbee] How to set lifetime for a local link

I'm trying to implement a method (sel) for an external (scraper::Html) struct. But compiler won't let me, because he doesn't know the lifetime of the local &select borrow. I guess I should somehow make it 'a, but I can't figure out how to do this. For now I could only collect nodes into Vec in sel_vec, but it doesn't feel quite idiomatic. Any suggestions?

use scraper::{html::Select, ElementRef, Html, Selector};

trait Sel {
    fn sel<'a, 'b>(&'a self, selector: &'b str) -> Select<'a, 'b>;
    fn sel_vec<'a, 'b>(&'a self, selector: &'b str) -> Vec<ElementRef<'_>>;
}

impl Sel for Html {
    fn sel<'a, 'b>(&'a self, selector: &'b str) -> Select<'a, 'b> {
        let selector = Selector::parse(selector).unwrap();
        // rustc: cannot return value referencing local variable `selector`
        self.select(&selector)
    }

    fn sel_vec<'a, 'b>(&'a self, selector: &'b str) -> Vec<ElementRef<'_>> {
        let selector = Selector::parse(selector).unwrap();
        self.select(&selector).collect()
    }
}

This is not a problem that can be fixed by changing some lifetime annotations.

The value selector lives as a local variable inside of sel, thus it can only be borrowed for a lifetime contained inside of the sel function. On the other hand, .select creates a Select<'a, 'b> using a &' b Selector. That is, it needs to be able to borrow the Selector for a lifetime 'b which in your case is a parameter to the function sel and lifetime parameters (almost*) always live longer than the function they’re attached to.
* This is perhaps, technically, more of a rule of thumb but you can’t avoid it in this case. I don’t want to go into "variance" at this point.

In a more intuitive way ignoring lifetimes, what’s going on is that a Select contains a reference to the Selector that you passed to select to create it. Thus returning a Select built from a Selector in a local variable is impossible (because you can’t return references to local variables). You need to restructure your code here unfortunately. One option is continuation-passing / call-back style, allowing the caller to have access to the Select through a closure. Something like this:

use scraper::{html::Select, ElementRef, Html, Selector};

trait Sel {
    fn sel<'a, F, R>(&'a self, selector: &str, f: F) -> R
    where
        F: FnOnce(Select<'a, '_>) -> R;
    fn sel_vec(&self, selector: &str) -> Vec<ElementRef<'_>> {
        self.sel(selector, |s| s.collect())
    }
}

impl Sel for Html {
    fn sel<'a, F, R>(&'a self, selector: &str, f: F) -> R
    where
        F: FnOnce(Select<'a, '_>) -> R,
    {
        let selector = Selector::parse(selector).unwrap();
        f(self.select(&selector))
    }
}
<- For clarification, here’s the same "Sel" trait with explicit lifetimes.
trait Sel {
    fn sel<'a, 'b, F, R>(&'a self, selector: &'b str, f: F) -> R
    where
        F: for<'c> FnOnce(Select<'a, 'c>) -> R;
    fn sel_vec<'a, 'b>(&'a self, selector: &'b str) -> Vec<ElementRef<'a>>;
}

But of course the possibilities of code refactoring are infinite and you might come up with a more suitable solution if you don’t like this one.

2 Likes

Thanks, @steffahn, so if I understand it, Select struct doesn't have a place to keep selector as it takes only its borrow. So if I'd like to avoid a new scope the closure brings in, should I then wrap the result into a struct to hold selector like this:

struct SelectWrapper<'a> {
    selector: Selector,
    html: &'a Html,
}

impl<'a> SelectWrapper<'a> {
    fn select(&self) -> Select<'_, '_> {
        self.html.select(&self.selector)
    }
}

trait Sel {
    fn sel(&self, selector: &str) -> SelectWrapper<'_>;
}

impl Sel for Html {
    fn sel(&self, selector: &str) -> SelectWrapper<'_> {
        let selector = Selector::parse(selector).unwrap();
        SelectWrapper {
            selector,
            html: self,
        }
    }
}

Yes, this would work, too.

By the way, if your goal is just syntactically avoid extra nestings (and callbacks would be okay otherwise), you could also look into @Yandros’s new with_locals crate.

use scraper::{html::Select, ElementRef, Html, Selector};
use with_locals::with;

trait Sel {
    #[with('local)]
    fn sel<'a>(&'a self, selector: &str) -> Select<'a, 'local>;
    #[with]
    fn sel_vec(&self, selector: &str) -> Vec<ElementRef<'_>> {
        #[with] let s = self.sel(selector);
        s.collect()
    }
}

impl Sel for Html {
    #[with('local)]
    fn sel<'a>(&'a self, selector: &str) -> Select<'a, 'local> {
        let selector = Selector::parse(selector).unwrap();
        self.select(&selector)
    }
}

@Yandros - your macro does not work correctly if I try to elide the lifetime 'a, as in

#[with('local)]
fn sel(&self, selector: &str) -> Select<'_, 'local>;
2 Likes

Thanks for pointing that out :slightly_smiling_face: Indeed given the unsugaring, this is something that the macro needs to handle manually. I've filed an issue for this, and will tackle it when I have the time.