Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rust reference dropped here while still borrowed

Tags:

rust

lifetime

The issue: I'm getting a "XXXXXXX dropped here while still borrowed" error from a method where XXXXXXX is valid to the end of the method (which is fine), but Rust is unnecessarily expecting it to live as long as the input references because those have an explicit lifetime.

Here's some setup information: I have a struct with iterates all found query within text content.

pub struct QueryRangeItr<'a> {
    inverted: bool,
    query: &'a str,
    content: &'a str,
}

It takes a reference of the query and content and expects the instance to last as long as the query/content. So far, so good.

impl<'a> QueryRangeItr<'a> {
    fn new(query: &'a str, content: &'a str, inverted: bool) -> QueryRangeItr<'a> {
        Self {
            inverted,
            query,
            content,
        }
    }
}

I have a static method that allows you to perform a transformation on each found query. It takes a closure and applies it to the queries, then gives you a new String which has no lifetime dependencies. Because the return value has no lifetime, I don't actually want to bring lifetimes to the query and content here, but if I don't, Rust tells me I need to... thus, they have lifetime requirements. By itself, this works fine.

The problem occurs on another static method that calls transform twice to transform both the query and non-query content. It first calls transfrom to apply one closure to found query. The results are fed back to another call of the transform method, but this time to apply a transform to the rest of the content.

impl<'a> QueryRangeItr<'a> {
    pub fn transform<T>(
        query: &'a str,
        content: &'a str,
        transform: T,
        inverted: bool,
    ) -> String where T: Fn(&str) -> String {
        let selects = Self::new(query, content, true);
        // ...
        // returns a `String` with no lifetime dependency on input params
    }

    pub fn transform_all<TQ, TNQ>(
        query: &'a str,
        content: &'a str,
        transform_query: TQ,
        transform_non_query: TNQ,
    ) -> String
        where
            TQ: Fn(&str) -> String,
            TNQ: Fn(&str) -> String,
    {
        let transformed_content = Self::transform(query, content, &transform_query, false);
        let transformed_query = transform_query(query);
        let transformed = Self::transform(&transformed_query, &transformed_content, transform_non_query, true); // <--- Rust expects `transformed_content` and `transformed_query` to match the lifetime of `query` and `content`
        transformed
    }

}

transformed_content and transformed_query both die at the end of transform_all... which makes sense, but Rust wants them to last as long as the input params query and content ('a)... and the won't.

I don't actually need them to live any longer. Once I get transformed back, I have no further need for them. However, having lifetimes on query and content make Rust think they need to last longer than they really need to and I get this error:

115 | impl<'a> QueryRangeItr<'a> {
    |      -- lifetime `'a` defined here
...
200 |         let transformed = Self::transform(&transformed_query, &transformed_content, transform_non_query, true);
    |                           ------------------------------------^^^^^^^^^^^^^^^^^^^^----------------------------
    |                           |                                   |
    |                           |                                   borrowed value does not live long enough
    |                           argument requires that `transformed_content` is borrowed for `'a`
201 |         transformed
202 |     }
    |     - `transformed_content` dropped here while still borrowed

and if I remove lifetimes I get this error:

error[E0621]: explicit lifetime required in the type of `query`
   --> src/range/query_range_iterator.rs:130:23
    |
125 |         query: &str,
    |                ---- help: add explicit lifetime `'a` to the type of `query`: `&'a str`
...
130 |         let selects = Self::new(query, content, invert);
    |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ lifetime `'a` required

And, if I try to define a different lifetime that 'a ('b or '_) for my transform methods, I get something like this:

error[E0521]: borrowed data escapes outside of associated function
   --> src/range/query_range_iterator.rs:130:23
    |
126 |         content: &'_ str,
    |         ------- `content` is a reference that is only valid in the associated function body
...
130 |         let selects = Self::new(query, content, invert);
    |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `content` escapes the associated function body here

What I want to do is find a way to prove to Rust that I don't need transformed_content and transformed_query as long as it thinks.

Any thoughts?

like image 812
Justin Reusch Avatar asked Oct 25 '20 05:10

Justin Reusch


1 Answers

Replace

 = Self::transform(&transformed_query, &transformed_content, ...
// ^^^^

with

 = QueryRangeItr::transform(&transformed_query, &transformed_content, ...
// ^^^^^^^^^^^^^

By using Self, you're using the same lifetime 'a for everything. And as you've pointed out, transformed_content and transformed_query only live within the function, so their lifetime is definitely shorter and cannot match 'a. Replacing Self with QueryRangeItr allows the compiler to pick a new lifetime 'a for the call.

like image 134
kmdreko Avatar answered Sep 19 '22 05:09

kmdreko