1

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
}
6
  • 1
    Besides the borrow checking error... how is this function ever going to return anything? It will just always return an empty vec. Is it just an incomplete simplified version of the actual function? Commented Nov 1, 2023 at 20:08
  • Correct... incomplete and simplified. I would be satisfied with an empty vec. Commented Nov 1, 2023 at 20:26
  • 1
    Ah ok, just checking. In any case, you can simply change the return type to Vec<String, MethodRouter> to perform a copy so the borrow checker is happy. Commented Nov 1, 2023 at 20:39
  • Marco -- I've edited the code to make the omission clear. Thanks! Commented Nov 1, 2023 at 20:47
  • 1
    Nitpick: It is more efficient to call entry.file_type().is_dir() than entry.path().is_dir() as this saves a syscall (usually). Commented Nov 2, 2023 at 9:30

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

3 Comments

"a temporary value is, by definition, one that ceases existing once you leave the block." - OP is not returning a temporary though, nor is he putting any reference to the passed path in the returned vec, so the error given by the borrow checker seems kind of weird.
Well, they're claiming that they're doing so by specifying &str in the return of the function. But you're right that my response needs to be clarified.
Yoric -- super helpful -- that helps simplify my mental model of how references work. The info about PathBuf is also quite interesting and useful. Marco -- That was where I was getting stuck, too, is that I didn't understand how the vec ended up getting tied to path. I assume it's because path ends up being dir in recursed function calls, but my understanding of why that matters is still a little shaky.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.