2

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Boon
  • 1,871
  • 1
  • 21
  • 31

1 Answers1

5

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:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Thank you so much. This is my forth time trying to pick up Rust from Java. Thanks for your patience! – Boon Apr 01 '18 at 02:34
  • Is this still the correct way? Will some sort of `.map()` on `tmp` perhaps be more efficient? – Jay Jul 30 '20 at 18:12
  • @Jay where do you propose that `map` method is implemented? – Shepmaster Aug 03 '20 at 12:35
  • @Shepmaster Looking it up, looks like I got it confused with reduce. In Python it would be something like - `reduce(lambda orig_str, replace_pairs: orig_str.replace(*replace_pairs), [('word', new_word'), ('word2', 'new_word2')], 'word word2')` – Jay Aug 04 '20 at 11:04
  • 1
    @Jay sure, something [like this](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=aec2e34dc06f7c29d5c136ad3802d88e). I wouldn't expect any major performance differences. – Shepmaster Aug 04 '20 at 15:26