Boon
Boon

Reputation: 2151

How to repeatedly perform string replacement in a loop?

I am writing a method to loop through a (from, to) of a map and perform multiple rounds of tmp = tmp.replace(from, to). I am still trying to grasp the ownership concepts of Rust

#[macro_use]
extern crate lazy_static;

use std::collections::HashMap;

lazy_static! {
    static ref REPLACEMENTS: HashMap<&'static str, &'static str> = {
        let mut m = HashMap::new();
        m.insert("abc", "def");
        m.insert("com", "org");
        m
    };
}

fn replace_path_name(path: &str) -> &str {
    let mut tmp = path;

    for (from, to) in REPLACEMENTS.iter() {
        let a = *from;
        let b = *to;

        tmp = tmp.replace(a, b);
    }

    tmp
}

fn main() {}

This code gets me...

error[E0308]: mismatched types
  --> src/main.rs:22:15
   |
22 |         tmp = tmp.replace(a, b);
   |               ^^^^^^^^^^^^^^^^^
   |               |
   |               expected &str, found struct `std::string::String`
   |               help: consider borrowing here: `&tmp.replace(a, b)`
   |
   = note: expected type `&str`
              found type `std::string::String`

The extra a and b are my attempts to get by why Rust made from and to become &&str.

Upvotes: 2

Views: 1749

Answers (1)

Shepmaster
Shepmaster

Reputation: 431789

The first problem is your return value: &str. You are returning a reference to something, but what will own that value? You cannot return a reference to a local variable.

The second problem is the return type of str::replace, which is a String, not a &str. That's the cause of your error message: you are attempting to store a String in a variable where only a &str can be stored. You cannot do that.

The easiest fix is not the most efficient; unconditionally create a String:

fn replace_path_name(path: &str) -> String {
    let mut tmp = String::from(path);

    for (from, to) in REPLACEMENTS.iter() {
        tmp = tmp.replace(from, to);
    }

    tmp
}

You could also use a type like Cow to save a little bit of allocation in some cases:

use std::borrow::Cow;

fn replace_path_name(path: &str) -> String {
    let mut tmp = Cow::from(path);

    for (from, to) in &*REPLACEMENTS {
        tmp = tmp.replace(from, to).into();
    }

    tmp.into()
}

Which can even be returned so that no allocation occurs if no replacements exist:

use std::borrow::Cow;

fn replace_path_name(path: &str) -> Cow<str> {
    let mut tmp = Cow::from(path);

    for (from, to) in &*REPLACEMENTS {
        tmp = tmp.replace(from, to).into();
    }

    tmp
}

Or the functional equivalent using Iterator::fold:

use std::borrow::Cow;

fn replace_path_name(path: &str) -> Cow<str> {
    REPLACEMENTS
        .iter()
        .fold(Cow::from(path), |s, (from, to)| s.replace(from, to).into())
}

It's unfortunate that str::replace doesn't return a Cow<str>. If it did, no allocations would take place if no replacements were made.

See also:

Upvotes: 5

Related Questions