How to fix "cannot borrow `foo` as mutable because it is also borrowed as immutable" inside a pattern matching block?

    let mut url = Url::parse(link)?;

    let strip_query_domains = HashSet::from(["zhihu.com", "v2ex.com", "twitter.com"]);
    if let Some(Host::Domain(domain)) = url.host() {
        if domain == "vxtwitter.com" {
            url.set_host(Some("twitter.com"))?;
        }

        if strip_query_domains.contains(domain) {
            url.set_query(None);
        }
    }

I can understand why this error is happening. And I intended to test the domains after turning the "vxtwitter.com" into "twitter.com".

But how can I fix this without breaking the if let block into two blocks like?

    if let Some(Host::Domain(domain)) = url.host() {
        if domain == "vxtwitter.com" {
            url.set_host(Some("twitter.com"))?;
        }
    }

    if let Some(Host::Domain(domain)) = url.host() {
        if strip_query_domains.contains(domain) {
            url.set_query(None);
        }
    }

I don't know if you'd consider it better or worse, but one way is:

    let (host, strip_query) = match url.host() {
        Some(Host::Domain("vxtwitter.com")) => (Some("twitter.com"), false),
        Some(Host::Domain(domain)) => (None, strip_query_domains.contains(domain)),
        _ => (None, false),
    };

    if let Some(host) = host {
        url.set_host(Some(host))?;
    }

    if strip_query {
        url.set_query(None);
    }

(I had to make some assumptions about the function and method signatures involved.)

Hi, sorry for the missing information. The Url struct is from Url in url - Rust .

And I'd like to first transform the vxtwitter.com links into twitter.com, then strip the query together with the links originally twitter.com and other some links.

For example,

https://twitter.com/abc?a=1
https://twitter.com/def?b=2
https://vxtwitter.com/hij?c=3
https://v2ex.com/xyz?d=4

First turn them into

https://twitter.com/abc?a=1
https://twitter.com/def?b=2
https://twitter.com/hij?c=3
https://v2ex.com/xyz?d=4

Then

https://twitter.com/abc
https://twitter.com/def
https://twitter.com/hij
https://v2ex.com/xyz

I'm not sure if I can do that in single if let block when matching the host. Splitting into two block helps, but I will have to add more blocks if there are even some more transformations later.

There's no way to hold on to the domain &str that is borrowed from the Url while also modifying the Url.

It's true more transformations may require more blocks if you keep everything in one function, but as things get long I'd suggest going the opposite direction and encapsulate more into bite-sized functions. E.g.

// This one could grow unbounded as you add replacements, but
// is basically just a list
fn host_replacement(url: &Url) -> Option<&'static str> {
    url.host_str().and_then(|host| match host {
        "vxtwitter.com" => Some("twitter.com"),
        "what.ever" => Some("thing.else"),
        _ => None
    })
}

// This (optional) one never changes
fn maybe_replace_host(url: &mut Url) -> bool {
    if let Some(host) = host_replacement(&url) {
        url.set_host(Some(host)).expect("Invalid host");
        true
    } else {
        false
    }
}

// Transformations become one-liners where actually used
fn actually_use_it(mut url: Url) {
    maybe_replace_host(&mut url);
}
1 Like

That's precisely because you have to split your code into many separate blocks and maybe even separate functions anyway.

I have seen lots of such scripts in languages where what you are planning to write is possible (JavaScript, Python, etc). And if such script was actually valuable enough to survive a year or two it invariably reaches the state of “it's complete mess, we couldn't touch it anymore without it falling aparts, it's time to throw it away and rewrite from scratch”.

The only way to avoid that fate is to write script like you would write it in Rust: with separate passes and separate functions. And the fact that Rust makes you do that… it's Rust advantage, not weakness.

Now, you may ask: what if I'm writing code for one use and don't plan to keep it around for a year, or two? In that case the advice is just to use not Rust but something else.

While anything can be written in Rust not everything should be written in Rust. Rust doesn't work for quick-and-dirty, let-me-write-and-run-it-once code.

Over time I shift more and more my local scripts to Rust, but JavaScript and Python are still something I reach out for truly write-once-run-once-and-forget cases.

2 Likes

Clone the url.

Thanks for the suggestion! The code looks pretty clean and reusable.

That lights me up.

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.