Borrowing problem in function to get path of an url

Hello,
I'm stuck on a function I'm writing, I want to get the path of the url string passed to arguments.
I did this :

fn getPath(url: &str) -> &str{
    match Url::parse(&url) {
        Ok(parsed) => parsed.path(),
        Err(_err) => ""
    }
}

But I have this error that I don't manage to solve :

error[E0515]: cannot return value referencing local variable `parsed`
   --> src/main.rs:303:23
    |
303 |         Ok(parsed) => parsed.path(),
    |                       ------^^^^^^^
    |                       |
    |                       returns a value referencing data owned by the current function
    |                       `parsed` is borrowed here

I tried the reference via &, copy and clone but I can't make it work...

Any reason you're not returning a String instead? Returning &str honestly seems like more trouble than it's worth in a use case like this.

As far as I can guess the &str you are trying to return is not a substring of something available in the caller. So you can't exactly return that &str as it wouldn't exist after the get_path function ends.

1 Like
fn get_path(url: &str) -> String {
    match Url::parse(&url) {
        Ok(parsed) => parsed.path().to_string(),
        Err(_) => "".to_string(),
    }
}

Where is that Url object coming from? It’s not defined in std. As the error is coming from a call to one of its methods, it’d be helpful to look at its documentation and type signature.

Think it's this.

Crate

Digging into that library a bit, the Url object doesn’t keep a reference to the original string. Instead, the parse method converts it into a normalized form and stores that as a String. The &str returned by Url::path() is a reference to this internal buffer, and there’s no guarantee that character sequence will even appear in the original input.

For this method to return an &str, it either needs to be rewritten without the Url crate or use something like Box::leak() to keep the intermediate result alive forever. Returning a String seems like the more sensible option.

Thank you @zireael9797 and @2e71828 ,
I completely forgot about unwrap_or !
Thanks for the help I think I'll use that !

I tried the code you said but it doesn't work since "" is not a url .
and it would return the URL and not the path
I tried :

fn getPath(url: &str) -> &str{
    let url = Url::parse(&url).unwrap_or("");
    return url.path()
}

But i got

error[E0308]: mismatched types
   --> src/main.rs:302:42
    |
302 |     let url = Url::parse(&url).unwrap_or("");
    |                                          ^^ expected struct `url::Url`, found `&str`

Edit :
I removed the function and integrate directly my code in the curren function but the solution I found is kinda horrible.
Is there a better way to do that :

lines
            .filter(|url| {
                !blacklist.iter().any(|ext| {
                    Url::parse(&url)
                        .unwrap_or(Url::parse("https://fakeurl.com/").unwrap())
                        .path()
                        .ends_with(ext)
                })
            })
            .collect()

Sorry yeah, my bad. I forgot you need the .path() from the parsed not the parsed itself

Basically you go with my initial solution

fn get_path(url: &str) -> String {
    match Url::parse(&url) {
        Ok(parsed) => parsed.path().to_string(),
        Err(_) => "".to_string(),
    }
}

Or I'd recommend not using the function at all, just use it directly. Saves all the unnecessary conversion to String from &str

let path = match Url::parse(&url) {
        Ok(parsed) => parsed.path(),
        _ => "",
    }

It's too small to refactor IMO

I used the function because it's in a .filter() and I had an error with the match in it with borrowing and it's more readable .
Thanks for your help !

1 Like

FWIW I think this is how I'd handle the closure

.filter(|url| {
            Url::parse(&url)
                .map(|url| !blacklist.iter().any(|ext| url.path().ends_with(ext)))
                .unwrap_or(false)
        })

Basically, parse the Url only once (the compiler might optimize it that way in your closure anyway, but I'm not sure...it might parse it again for every ext). Then use the parsed value through map which converts the Result<Url, _> into Result<bool, _> that we can then call unwrap_or on (unwrap_or_default is also an option since bool's default is false). In general map is a great tool when you want to run an operation on the Ok value of a result, avoiding "placeholder" values like the fakeurl.

If it's easier for you to have it in a function, I'd create a dedicated filtering function fn url_path_ends_with_blacklisted(url: &str, blacklist: &Vec<String>) -> bool, or whatever blacklist's type is, after which you'd have
.filter(|url| url_path_ends_with_blacklisted(&url, &blacklist))

1 Like

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.