functionrust

Unable to create a local function because "can't capture dynamic environment in a fn item"


Is there any way to create a local function like this Python code?

def h():
    final = []
    def a():
        for i in range(5):
            final.append(i)
    a()
    return final

I tried it, but failed:

fn h() -> Vec<i32> {
    let mut ff = vec![];
    fn a() {
        for i in 0..5 {
            ff.push(i)
        }
    };
    a();
    ff
}
 error[E0434]: can't capture dynamic environment in a fn item; use the || { ... } closure form instead
 --> src/main.rs:5:13
  |
5 |             ff.push(i)
  |             ^^

Solution

  • Functions in Rust don't capture variables from the surrounding environment, period. A "local" function in Rust is really just a global function that isn't globally visible; it can't do anything more than any other global function.

    Instead, Rust has closures which are distinct from functions in that they do capture variables from their environment. That would look like this:

    fn h() -> Vec<i32> {
        let mut ff = vec![];
        let mut a = || {
            for i in 0..5{
                ff.push(i)
            }
        };
        a();
        ff
    }
    

    Three things to note with this. Firstly, append is not what you want, you want push. You should check the documentation for Vec to see what's available and what methods do. Secondly, you have to make a mutable because it's mutating something it captured (see also this answer about Fn, FnMut, and FnOnce). Third, it won't compile:

    error[E0505]: cannot move out of `ff` because it is borrowed
     --> <anon>:9:9
      |
    3 |         let mut a = || {
      |                     -- borrow of `ff` occurs here
    ...
    9 |         ff
      |         ^^ move out of `ff` occurs here
    

    The problem is that by creating the closure, you had to give it a mutable borrow to ff. However, that borrow prevents anyone else from moving or otherwise messing with ff. You need to shorten the length of time this borrow exists:

    fn h() -> Vec<i32> {
        let mut ff = vec![];
        {
            let mut a = || {
                for i in 0..5{
                    ff.push(i)
                }
            };
            a();
        }
        ff
    }
    

    This works, but is kinda clunky. It's also unnecessary; the above could more cleanly be rewritten by just passing the borrow to ff into a local function explicitly:

    fn h() -> Vec<i32> {
        let mut ff = vec![];
        fn a(ff: &mut Vec<i32>) {
            for i in 0..5{
                ff.push(i)
            }
        }
        a(&mut ff);
        ff
    }
    

    This last one is the best (if you're able to use it), because it keeps it clean when and why ff is being borrowed.