Cannot return value referencing function parameter with Neon project

Apologies as I am a beginner with Rust and this might be an obvious question.

I'm using neon to write a very basic native NodeJS module for Electron. However, I am at a complete loss on how to move this very basic piece of code into its own function.

Here is a fully functioning lib.rs for neon that calls and returns the value of document.getElementById("asd"):

use neon::prelude::*;

fn hello(mut cx: FunctionContext) -> JsResult<JsObject> {
    let doc: Handle<JsObject> = cx.global("document")?;
    let func: Handle<JsFunction> = doc.get(&mut cx, "getElementById")?;
    func.call_with(&mut cx)
        .this(doc)
        .arg(cx.string("asd"))
        .apply(&mut cx)
}

#[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> {
    cx.export_function("hello", hello)?;
    Ok(())
}

I would like to write a get_element function that calls and returns the value of document.getElementById, so I tried this:

fn get_element<'a>(cx: &'a mut FunctionContext<'a>, name: &str) -> JsResult<'a, JsObject> {
    let doc: Handle<JsObject> = cx.global("document")?;
    let func: Handle<JsFunction> = doc.get(cx, "getElementById")?;
    func.call_with(cx)
        .this(doc)
        .arg(cx.string(name))
        .apply(cx)
}

fn hello(mut cx: FunctionContext) -> JsResult<JsObject> {
    get_element(&mut cx, "asd")
}

But this results in the error:

Cannot return value referencing function parameter `cx` returns a value referencing data owned by the current function.

I understand what this error is trying to say, but I'm at a complete loss on how to fix this? How come the original version works, but placing everything in a separate function ruins it? If the value is referencing the temporary cx value, how come the original version works? Is there a way to communicate to the Rust compiler what I want?

Thanks!

As a general rule, please post the full output from cargo check (or cargo build) run in the terminal. There is usually more info in Rust compiler errors that provides clues.

1 Like

I figured it out, I was overzealous with the lifetimes since the return type was asking for a lifetime.

This works:

fn get_element<'a>(cx: &mut FunctionContext<'a>, name: &str) -> JsResult<'a, JsObject> {
    let doc: Handle<JsObject> = cx.global("document")?;
    let func: Handle<JsFunction> = doc.get(cx, "getElementById")?;
    func.call_with(cx).this(doc).arg(cx.string(name)).apply(cx)
}

fn hello(mut cx: FunctionContext) -> JsResult<JsObject> {
    get_element(&mut cx, "asd")
}

I'm still not sure why this works, so if someone is able to explain, that would be wonderful.

Here is the cargo check without the lifetimes on the function:

error[E0106]: missing lifetime specifier
 --> src\lib.rs:6:65
  |
6 | ...(cx: &mut FunctionContext, name: &str) -> JsResult<JsObject> {
  |         --------------------        ----             ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from one of `cx`'s 2 lifetimes or `name`
help: consider introducing a named lifetime parameter
  |
6 | fn get_element<'a>(cx: &'a mut FunctionContext<'a>, name: &'a str) -> JsResult<'a, JsObject> {
  |               ++++      ++                    ++++         ++
           +++

And here is the cargo check with the original code:

error[E0515]: cannot return value referencing function parameter `cx`
  --> src\lib.rs:13:5
   |
13 |     get_element(&mut cx, "asd")
   |     ^^^^^^^^^^^^-------^^^^^^^^
   |     |           |
   |     |           `cx` is borrowed here
   |     returns a value referencing data owned by the current function
1 Like

The cx param of hello is an owned object (not a reference), so the reference &mut cx has a lifetime limited to the hello function. This lifetime is 'a in get_element.

The signature of get_element is saying that the result has lifetime 'a. But that lifetime ends when hello ends, so something with that lifetime can't be returned by hello.

By removing the 'a in cx: &'a mut in get_element, you've removed that artificial limitation on the lifetime of the result.

That's my simplistic way of seeing it, anyway.

TLDR: never write &'a mut Foo<'a>

A FunctionContext<'a> cannot live for longer than 'a, since when 'a ends the FunctionContext<'a> would contain an invalid borrow.

On the other end however &'a mut T requires T to live for at least 'a, as the T must be valid for as long as the mutable reference exists (which is 'a).

Putting them together, &'a mut FunctionContext<'a> requires the FunctionContext<'a> to live for exactly 'a, that is for exactly as long as the mutable reference is valid. This ends up borrowing the FunctionContext<'a> for as long as it exists. This is not an error by itself, but any use of the FunctionContext<'a> after the use of that borrow will be an error (often pretty cryptic, like you experienced).

2 Likes

This is a great explanation, thank you!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.