How to Reuse a Mutably Borrowed Variable in Rust After Passing It to a Function?

Hello friends. I've been working with the OXC library recently, and I'm facing an issue that I can't seem to solve.

The problem is that I declare a mutable variable and pass it to another function, and then I'm no longer able to use it afterwards.

pub fn extend_hook_object_to_ast<'a>(
    file_path: &str,
    allocator: &'a Allocator,
) -> Result<String, String> {
    let mut program = source_to_ast(file_path, allocator)?;

    if let Some(hooks_property) = find_hooks_property(&mut program) {
        ...
    } else {
        return Err("liveSocket not found in the AST".to_string());
    }

    let codegen = Codegen::new();
    let generated_code = codegen.build(&program).code;

    Ok(generated_code)
}

fn find_hooks_property<'a>(program: &'a mut Program<'a>) -> Option<&'a mut Expression<'a>>{}

As you can see, I've created program and passed it to the function find_hooks_property, but now I can't return it at the end of this function, i.e., in the following line:

let generated_code = codegen.build(&program).code;

And I'm getting the following error:

rustc: cannot borrow `program` as immutable because it is also borrowed as mutable
immutable borrow occurs here

Since I'm new to Rust, I checked if methods like clone or owned exist for program, but unfortunately, they don't (at least if I'm on the right track).

What do you suggest I do?

Thanks in advance.


Here's the link to the functions.

The whole project is at the link below, just a few files.

The problem is your function signature here:

fn find_hooks_property<'a>(program: &'a mut Program<'a>) -> Option<&'a mut Expression<'a>> {

You probably want this instead:

fn find_hooks_property<'a>(program: &mut Program<'a>) -> Option<&mut Expression<'a>>

&'a mut Something<'a> is not a particularly useful construction because it always results in the locked-forever behaviour that you're seeing:

  • The <'a> type annotation indicates that Something<'a> is only valid within the region 'a, probably due to holding some kind of reference.
  • The 'a in &'a mut ... says that your borrow is exclusive for the entirety of the region 'a.

These two facts combined mean that you've exclusively borrowed the Something for the entire region in which it is valid and therefore can't use it anymore except through the mutable reference you've created.

2 Likes

Hi, If I change this, I get this error

error[E0621]: explicit lifetime required in the type of `program`
   --> src/parser/ast.rs:226:5
    |
225 |   fn find_hooks_property<'a>(program: &mut Program<'a>) -> Option<&'a mut Expression<'a>> {
    |                                       ---------------- help: add explicit lifetime `'a` to the type of `program`: `&'a mut oxc::ast::ast::Program<'a>`
226 | /     program.body.iter_mut().find_map(|node| {
227 | |         let var_decl = match node {
228 | |             Statement::VariableDeclaration(var_decl) => var_decl,
229 | |             _ => return None,
...   |
247 | |         })
248 | |     })
    | |______^ lifetime `'a` required

It should be noted, I will need to change some lines of ast in this functuon, so I call the iter_mut()

You also need to remove the 'a from &'a mut... in the return type.


I'm actually not completely certain of the lifetime elision rules here; if that still errors, you may need to use two lifetimes explicitly:

fn find_hooks_property<'b, 'a:'b>(program: &'b mut Program<'a>)->Option<&'b mut Expression<'a>>

This is an extremely unfortunate help message from the compiler, as it's suggesting a change that is pretty much guaranteed to cause more problems down the line.

Still I have error
I changed this line:

fn find_hooks_property<'b, 'a: 'b>(program: &'b mut Program<'a>) -> Option<&'b mut Expression<'a>> {

and add move to my map

program.body.iter_mut().find_map(move |node| {

and error

error: lifetime may not live long enough
   --> src/parser/ast.rs:227:5
    |
226 |   fn find_hooks_property<'b, 'a: 'b>(program: &'b mut Program<'a>) -> Option<&'b mut Expression<'a>> {
    |                          --  -- lifetime `'a` defined here
    |                          |
    |                          lifetime `'b` defined here
227 | /     program.body.iter_mut().find_map(move |node| {
228 | |         let var_decl = match node {
229 | |             Statement::VariableDeclaration(var_decl) => var_decl,
230 | |             _ => return None,
...   |
248 | |         })
249 | |     })
    | |______^ function was supposed to return data with lifetime `'a` but it is returning data with lifetime `'b`
    |
    = help: consider adding the following bound: `'b: 'a`
    = note: requirement occurs because of a mutable reference to `Expression<'_>`
    = note: mutable references are invariant over their type parameter
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance

You probably also need to fix the signatures of get_new_expression, get_object_expression, and get_property_by_key which have the same issue (and anywhere else that you use &'a mut ...<'a>)


(In addition, it's also possible that I've gotten the bounds backwards...)


&'a Something<'a> is also usually wrong, but it's not quite as fatal an error as the &mut version due to how variance differs between & and &mut.

2 Likes

I changed it and on that place I got error for this line

Expression::NewExpression(expr) => Some(expr),

Doc

oxc_ast::ast::js::Expression
NewExpression(Box<'a, NewExpression<'a>>)

There have been a lot of changes since the OP. Do you have a current block of code that's erroring, with the full error message?

1 Like

Hi @quinedot
This is my code error

error[E0502]: cannot borrow `program` as immutable because it is also borrowed as mutable
   --> src/parser/ast.rs:219:40
    |
177 |     if let Some(hooks_property) = find_hooks_property(&mut program) {
    |                                                       ------------ mutable borrow occurs here
...
219 |     let generated_code = codegen.build(&program).code;
    |                                        ^^^^^^^^
    |                                        |
    |                                        immutable borrow occurs here
    |                                        mutable borrow later used here

For more information about this error, try `rustc --explain E0502`.
error: could not compile `mishka_parser_nif` (lib test) due to 1 previous error

I reverted the changes suggested in this post to the original code, which I linked to.

From this function

If you have time, please download the project and test it. It's not more than two or three files.

I downloaded it. You still have all of these "borrow forever" problems in the branch:

$ rg "&'\w+ mut \w+<'\w+" src/
src/parser/ast.rs
225:fn find_hooks_property<'a>(program: &'a mut Program<'a>) -> Option<&'a mut Expression<'a>> {
259:    decl: &'a mut VariableDeclarator<'a>,
260:) -> Option<&'a mut NewExpression<'a>> {
267:fn get_object_expression<'a>(arg: &'a mut Argument<'a>) -> Option<&'a mut ObjectExpression<'a>> {
275:    property: &'a mut ObjectPropertyKind<'a>,
277:) -> Option<&'a mut Expression<'a>> {

These need to be fixed, there's no other progress you can make until that is done. This allows cargo check to pass:

diff --git a/native/mishka_parser_nif/src/parser/ast.rs b/native/mishka_parser_nif/src/parser/ast.rs
index 34b61db..9ee2937 100644
--- a/native/mishka_parser_nif/src/parser/ast.rs
+++ b/native/mishka_parser_nif/src/parser/ast.rs
@@ -222,7 +222,9 @@ pub fn extend_hook_object_to_ast<'a>(
     Ok(generated_code)
 }
 
-fn find_hooks_property<'a>(program: &'a mut Program<'a>) -> Option<&'a mut Expression<'a>> {
+fn find_hooks_property<'short, 'long>(
+    program: &'short mut Program<'long>,
+) -> Option<&'short mut Expression<'long>> {
     program.body.iter_mut().find_map(|node| {
         let var_decl = match node {
             Statement::VariableDeclaration(var_decl) => var_decl,
@@ -255,26 +257,28 @@ fn is_target_variable(decl: &VariableDeclarator, name: &str) -> bool {
         .map_or(false, |binding| binding.name == name)
 }
 
-fn get_new_expression<'a>(
-    decl: &'a mut VariableDeclarator<'a>,
-) -> Option<&'a mut NewExpression<'a>> {
+fn get_new_expression<'short, 'long>(
+    decl: &'short mut VariableDeclarator<'long>,
+) -> Option<&'short mut NewExpression<'long>> {
     match decl.init.as_mut()? {
         Expression::NewExpression(expr) => Some(expr),
         _ => None,
     }
 }
 
-fn get_object_expression<'a>(arg: &'a mut Argument<'a>) -> Option<&'a mut ObjectExpression<'a>> {
+fn get_object_expression<'short, 'long>(
+    arg: &'short mut Argument<'long>,
+) -> Option<&'short mut ObjectExpression<'long>> {
     arg.as_expression_mut().and_then(|expr| match expr {
         Expression::ObjectExpression(boxed_obj_expr) => Some(boxed_obj_expr.as_mut()),
         _ => None,
     })
 }
 
-fn get_property_by_key<'a>(
-    property: &'a mut ObjectPropertyKind<'a>,
+fn get_property_by_key<'short, 'long>(
+    property: &'short mut ObjectPropertyKind<'long>,
     key_name: &str,
-) -> Option<&'a mut Expression<'a>> {
+) -> Option<&'short mut Expression<'long>> {
     match property {
         ObjectPropertyKind::ObjectProperty(prop) => match &prop.key {
             PropertyKey::StaticIdentifier(key) if key.as_ref().name == key_name => {
3 Likes

I created an issue to improve diagnostics, which I assume is how you ended up in this situation.

5 Likes

You’ve been incredibly kind to me; thank you so much. The changes you showed with the diff were very educational for me.
I’ve watched many courses and read plenty of content, but it seems I still have significant weaknesses.
Do you recommend any resources?
Thank you in advance :heart: :heart:

1 Like

Yes, I recommend thoroughly reading through @quinedot's Learning Rust. It is one of the best "casual" sources I have come across that describes many common pitfalls with lifetimes.

The Reference and Nomicon are also excellent resources, but they are much too dense for casual reading.

3 Likes