Search code examples
rustborrow-checker

Return value from Rust function cannot reference local or temporary variable


Context

Having trouble getting this function working. I believe I understand what the errors are trying to tell me, but I don't understand how I'm committing the errors / how to resolve them.

The function is a recursive function that looks through a directory and its subdirectories and accumulates a vector of the path and an Axum MethodRouter (which I don't believe has any bearing on the actual issue I'm facing).

Error

The compiler error states "cannot return value referencing local variable path."

If I replace path.to_str() with path.clone().to_str(), the error becomes, "cannot return value referencing temporary value."

Code

fn assign_handlers(dir: Option<&str>) -> Vec<(&str, MethodRouter)> {
    //Check root dir validity
    let dir = match dir {
        Some(s) => s,
        None => return vec![],
    };

    //Look for routes in dir
    let dir_path = fs::read_dir(dir).expect(&format!("Unable to access directory '{}'", &dir));
    let mut routes = Vec::new();
    for entry in dir_path {
        match entry {
            Ok(entry) => {
                let path = entry.path();
                if path.is_dir() {
                    //Recurse into subdirectories
                    let subroutes = assign_handlers(path.to_str());
                    for route in subroutes {
                        routes.push(route)
                    }
                } else if path.is_file() {
                    // Cut for brevity (and because I don't know what goes here yet)
                }
            },
            Err(_) => {},
        }
    }

    routes
}

Solution

  • Rust is correct. Let's decode &str: the & means that it is a reference and str means that it is the contents of a string. In garbage-collected languages, references keep the object alive, which is convenient, but Rust has made a different choice, that is usually faster and more memory-efficient, and that can be used to find lots of bugs that the compiler would let pass in other languages: a reference does not keep an object alive.

    This is what the compiler tells you: a temporary value is, by definition, one that ceases existing once you leave the block. Since the return type that you specify in the function contains &str, the compiler trusts you and assumes that subroutes contains temporary values and that you're trying to return them. And since you can't return a temporary value, you get an error.

    So, what you should do is instead return something that is guaranteed to remain alive. This could be a String, for instance. Except, in this case, you already have something that is alive: a PathBuf - that's equivalent to a String, except it uses the encoding of your file system, as required by your operating system (many languages confuse a PathBuf and a String – this makes most applications written in these languages unusable in Japan, Korea or China, for instance).

    With these changes, you get:

    fn assign_handlers(dir: Option<&Path>) -> Vec<(PathBuf, MethodRouter)> {
        //Check root dir validity
        let dir = match dir {
            Some(s) => s,
            None => return vec![],
        };
    
        //Look for routes in dir
        let dir_path = fs::read_dir(dir).expect(&format!("Unable to access directory '{:?}'", &dir));
        let mut routes = Vec::new();
        for entry in dir_path {
            match entry {
                Ok(entry) => {
                    let path = entry.path();
                    if path.is_dir() {
                        //Recurse into subdirectories
                        let subroutes = assign_handlers(Some(&path));
                        for route in subroutes {
                            routes.push(route)
                        }
                    } else if path.is_file() {
    
                    }
                },
                Err(_) => {},
            }
        }
    
        routes
    }
    

    Also, I'm not sure why you pass an Option<&PathBuf>. I would suggest passing just a &PathBuf. This would simplify the code a bit.