Cannot return value referencing local variable

Hi, I'm new to rust and wasn't able to successfully solve this error by myself.

Playground: Rust Playground

I'm not sure if the code is correct as it's an attempt to shrink down the actual code to a minimal reproducible example. It should suffice to illustrate the problem though.

use std::collections::HashMap;

enum Value<'a> {
    Null,
    Symbol(&'a str),
    Number(&'a i32),
    Cons {
        car: &'a Value<'a>,
        cdr: Option<&'a Value<'a>>,
    },
}

#[derive(Clone)]
enum V<'a> {
    Null,
    Number(&'a i32),
    String(&'a str),
    Symbol(&'a str),
    Builtin(fn(Vec<V>, &'a S) -> V<'a>),
    Function(Vec<&'a str>, &'a Value<'a>),
}

struct S<'a> {
    i: HashMap<&'a str, V<'a>>,
    parent: Option<&'a S<'a>>,
}

struct ConsItr<'a> {
    cur: Option<&'a Value<'a>>,
}

impl<'a> Iterator for ConsItr<'a> {
    type Item = &'a Value<'a>;
    fn next(&mut self) -> Option<Self::Item> {
        match self.cur {
            Some(v) => match v {
                Value::Cons { car, cdr } => {
                    let next = car;
                    self.cur = match cdr {
                        Some(v) => Some(v),
                        None => None,
                    };
                    Some(next)
                },
                _ => None,
            },
            _ => None,
        }
    }
}

fn apply<'a>(body: &'a Value, scope: &'a S<'a>) -> V<'a> {
    let body_itr = ConsItr { cur: Some(body) };
    let mut ret = V::Null;
    for val in body_itr {
        ret = eval(&val, scope);
    }
    ret
}

fn eval<'a>(val: &'a Value, s: &'a S<'a>) -> V<'a> {
    match val {
        Value::Number(n) => V::Number(n),
        Value::Symbol(str) => V::Symbol(str),
        Value::Cons { car, cdr: _ } => {
            let car = eval(car, s);
            match car {
                V::Function(_params, body) => {
                    let new_scope = S {
                        i: HashMap::new(),
                        parent: Some(s),
                    };
                    // code elided...
                    apply(body, &new_scope)
                }
                _ => V::Null,
            }
        }
        _ => V::Null,
    }
}

fn main() {
    let m = S {
        i: HashMap::new(),
        parent: None,
    };

    let v = Value::Cons {
        car: &Value::Symbol("lambda"),
        cdr: Some(&Value::Cons {
            car: &Value::Cons {
                car: &Value::Null,
                cdr: None,
            },
            cdr: Some(&Value::Cons {
                car: &Value::Number(&1),
                cdr: None,
            }),
        }),
    };

    eval(&v, &m);
}

The error I'm getting is:

error[E0515]: cannot return value referencing local variable `new_scope`
  --> src/main.rs:74:21
   |
74 |                     apply(body, &new_scope)
   |                     ^^^^^^^^^^^^----------^
   |                     |           |
   |                     |           `new_scope` is borrowed here
   |                     returns a value referencing data owned by the current function

Any help is appreciated.

apply is defined as

fn apply<'a>(body: &'a Value, scope: &'a S<'a>) -> V<'a> { ... }

which means that the value that it returns has the lifetime of the shorter one of the two arguments given to body and scope (since both share the same lifetime parameter). Now when you do

match car {
    V::Function(_params, body) => {
        let new_scope = S {
            i: HashMap::new(),
            parent: Some(s),
        };
        // code elided...
        apply(body, &new_scope)
    } // <- new_scope destroyed here
    ... 
}

The shortest lifetime here is the one of new_scope, which only lives until leaving the scope of the match arm. However, the return value of apply is supposed to leave the match arm to then be returned from the function. This can't work, because new_scope would already have been destroyed by then.

Essentially, a very minimal example reproducing the same issue as in your code would be:

fn apply<'a>(scope: &'a u32) -> &'a u32 {
    body
}

fn eval<'a>(scope: &'a u32) -> &'a u32 {
    let new_scope = 123u32;
    apply(&new_scope)
}

fn main() {
    let other_local = 234u32;
    eval(&other_local);
}

Your Value, V, etc. types (or whatever they correspond to in your actual code) should not be holding references; they should own the data in their fields, so i32 instead of &i32, String instead of &str, Value instead of &Value<'_>. Types that hold references have their uses, but you should stay away from defining any yourself until you're fairly experienced with Rust, and even then your default when defining a new type should be to store owned data. If you overuse references in your struct or enum, and especially if you stick the same lifetime parameter 'a everywhere within a type (the compiler sometimes gives you bad advice that leads to this situation, unfortunately), you will get confusing compiler errors like the ones you're seeing now.

(If you're not sure what I mean by "owning" here, this chapter is a good place to start.)

A consequence of replacing reference fields with owning fields is that you'll need to use the owning pointer type Box in some places (see below) when you're defining a recursive type. (Read more about Box here.)

enum Value {
    Null,
    Symbol(String),
    Number(i32),
    Cons {
        // We need `Box` here because `Value` cannot contain a field of type `Value`
        // (what would the size of the type be if it did?)
        car: Box<Value>,
        cdr: Option<Box<Value>>,
    },
}

enum V {
    Null,
    Number(i32),
    String(String),
    Symbol(String),
    // We keep the reference here, because storing a value of type `fn(Vec<V>, &S) -> V`
    // (a function pointer) does not require storing a `&S`
    Builtin(fn(Vec<V>, &S) -> V),
    Function(Vec<String>, Value),
}

struct S {
    i: HashMap<String, V>,
    // `Box` needed here for the same reason as before
    parent: Option<Box<S>>,
}

struct ConsItr {
    cur: Option<Value>,
}

A solid rule of thumb is that if you ever find yourself writing &'a Something<'a>, you've gone wrong somewhere, because a type of that shape is very rarely encountered in correct code.

2 Likes

I'm not sure your example is sufficient though. As if I add a second lifetime to apply it will compile successfully.

ie.

fn apply<'a,'b>(_scope: &'b u32) -> &'a u32 {
    &1
}

fn eval<'a>(_scope: &'a u32) -> &'a u32 {
    let new_scope = 123u32;
    apply(&new_scope)
}

fn main() {
    let other_local = 234u32;
    eval(&other_local);
}

Whereas in my case I get this error:

error[E0623]: lifetime mismatch
  --> src/main.rs:56:26
   |
52 | fn apply<'a,'b>(body: &'a Value, scope: &'b S<'a>) -> V<'a> {
   |                                         ---------     -----
   |                                         |
   |                                         this parameter and the return type are declared with different lifetimes...
...
56 |         ret = eval(&val, scope);
   |                          ^^^^^ ...but data from `scope` is returned here

You changed the semantics there so that apply's return value is no longer tethered to the input lifetime -- that's why it's compiling.

Here's a playground link with your stuff compiling: Rust Playground

I split the borrows into two distinct lifetimes: one for the scope, and one for the values. This way, the lifetime of the scope no longer flows into the return value.

Edit: While we've solved this now, I do agree with what @cole-miller says. Particularly as a beginner, trying to build complex lifetime relations will often be rather frustrating. In general, it seems preferable for the Value type to be owning its data. If you need to have additional references in your scope struct, you can simply use std::rc::Rc for that.

1 Like

I appreciate the advice. I have an issue that a library I'm using returns references and I get a bunch of errors about moving out of shared references when I try to make my code not use references.

Could you give an example or two of the kind of issue you're running into? There's likely a way to solve them without putting references in all your types. For example, if the library gives you string slices &str you can convert them to owned strings String with .to_owned().

Thanks so much! I have one more error though in my actual code after applying this. I don't think I can make a reproducible test case, so if you care to look at the code I've posted it here: Rust Playground

I'm not sure if you'll be able to solve it without compiling though :frowning: as the playground doesn't support the lexpr crate.

And the error on this line:

_fn(args, scope)

is:

error[E0623]: lifetime mismatch
   --> src\main.rs:140:33
    |
97  |     scope: &'b Scope<'a, 'b>
    |            ----------------- this parameter and the return type are declared with different lifetimes...
98  | ) -> Val<'a> {
    |      -------
...
140 |                                 _fn(args, scope)
    |                                 ^^^^^^^^^^^^^^^^ ...but data from `scope` is returned here

I don't know if this is too much code to look through, but here is my attempt to drop the references: Rust Playground

I rushed it so I may have missed something.

I get an error at this line:

                        new_scope.put(*params.get(i).unwrap(), v);
error[E0507]: cannot move out of a shared reference
   --> src\main.rs:153:39
    |
153 |                         new_scope.put(*params.get(i).unwrap(), v);
    |                                       ^^^^^^^^^^^^^^^^^^^^^^^ move occurs because value has type `std::string::String`, which does not implement the `Copy` trait

Edit:

I changed the line to

                        new_scope.put(params.get(i).unwrap().to_string(), v);

and the code is actually working!

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.