Borrowing error passing a reference to a function?

The following function has an error that it returns a value referencing data owned by the current function.

The thing is, the code that is commented out compiles (and runs) just fine — I am just trying to refactor the commended out code to the method get_str().

fn read_ping_query<'a>(dict: HashMap<&'a str, Bencode<'a>>) -> Result<RPC<'a>> {
    let arg = match dict.get("a") {
        Some(Bencode::Dict(arg)) => arg,
        _ => anyhow::bail!("read_ping_query: arguments not found"),
    };

    // let node_id = match arg.get("id") {
    //     Some(Bencode::Str(node_id)) => node_id,
    //     _ => anyhow::bail!("read_ping_query: node_id not found"),
    // };
    let node_id = arg.get_str("id").context("read_ping_query: no node_id found")?;

    Ok(RPC::PingQuery { node_id })
}

Here is the error message:

error[E0515]: cannot return value referencing function parameter `dict`
  --> src/rpc.rs:39:5
   |
26 |     let arg = match dict.get("a") {
   |                     ------------- `dict` is borrowed here
...
39 |     Ok(RPC::PingQuery { node_id })
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function

Basically, the commented out code is a pattern I am using repeatably, so I tried to refactor it into a custom trait method get_str().

#[derive(Debug, PartialEq)]
pub enum Bencode<'a> {
    Int(i64),
    Str(&'a [u8]),
    List(Vec<Bencode<'a>>),
    Dict(HashMap<&'a str, Bencode<'a>>),
}

pub trait FromBencode {
    fn get_str(&self, key: &str) -> Option<&[u8]>;
}

impl<'a> FromBencode for HashMap<&str, Bencode<'a>> {
    fn get_str(&self, key: &str) -> Option<&[u8]> {
        match self.get(key) {
            Some(Bencode::Str(value)) => Some(value),
            _ => None,
        }
    }
}

As far as I can tell, what I am doing in the method get_str() is equivalent to code in the comments (that works fine).

Any help understanding why this get_str() method doesn't work is appreciated!

Your trait says that get_str borrows from self, and in read_ping_query, that corresponds to dict -- a local variable. You can't return borrows of local variables. If you make the elided lifetimes explicit, it looks like:

fn get_str<'s, 'k>(&'s self, key: &'k str) -> Option<&'s [u8]> {

Note how &self and the returned &[u8] have the same lifetime.

Instead, you're wanting to return (something containing) a copy of some borrow you already own, with lifetime 'a. For that to work with the trait, the trait will have to know about 'a.

// All that changed was the addition of a lifetime to the trait,
// and the signatures of the method

pub trait FromBencode<'a> {
    fn get_str(&self, key: &str) -> Option<&'a [u8]>;
}

impl<'a> FromBencode<'a> for HashMap<&str, Bencode<'a>> {
    fn get_str(&self, key: &str) -> Option<&'a [u8]> {
        match self.get(key) {
            Some(Bencode::Str(value)) => Some(value),
            _ => None,
        }
    }
}

Now after making the elided lifetimes explicit, it looks like

fn get_str<'s, 'k>(&'s self, key: &'k str) -> Option<&'a [u8]> {

Note how the returned lifetime is no longer connected to any input lifetime, so in read_ping_query you're no longer returning a borrow of dict.

Playground.

Lifetime elision documentation.

2 Likes

An associated type is an alternative to a lifetime parameter.

2 Likes

@quinedot offers the great answer and solution.

I want to make an additional remark. If you're not sure how method calls are going on, desugar them by yourself.

fn read_ping_query<'a>(dict: HashMap<&'a str, Bencode<'a>>) -> anyhow::Result<&'a [u8]> {
    let arg = match dict.get("a") {
        Some(Bencode::Dict(arg)) => arg,
        _ => anyhow::bail!("read_ping_query: arguments not found"),
    };
    // HashMap<&'a str, Bencode<'a>::get<'get, Q>(&'get HashMap<&'a str, Bencode<'a>, k: &Q) -> Option<&'get Bencode<'a>>
    // Bencode::Dict(arg): &'get Bencode<'a>
    // arg: &'get HashMap<&'a str, Bencode<'a>>

    let node_id = arg.get_str("id").context("read_ping_query: no node_id found")?;
    // HashMap<&str, Bencode<'a>>::get_str::<'get_str>(&'get_str Self, &str) -> Option<&'get_str [u8]>
    // 'get => 'get_str where 'get is shorter than 'a
    // leading to error[E0515]: cannot return value referencing function parameter `dict`
    //            returns a value referencing data owned by the current function

    Ok(node_id)
}
2 Likes

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.