[solved] Trying (and failing) to replace strings in a string from a map (E0277)

I have a little function that takes a string and a hash map of string keys and values. The idea is to replace every key string found in the original string with the corresponding value. Unfortunately, I get E0277. This tells me that I'm missing a trait but I don't understand why or how to fix it. Can anyone help please?
Playpen
And if there's a better way of doing this anyway, I'd be glad to know.

Can you share some code?

It is in the playpen link, but here it is inline:

use std::collections::HashMap;

type EnvMap = HashMap<String, String>;

fn main() {
    let mut e = HashMap::new();
    e.insert("$A".to_string(), "Alpha".to_string());
    e.insert("$B".to_string(), "Bravo".to_string());
    let value = expand_env("Nothing to expand.", &e);
    println!("{}", value);
    let value = expand_env("Something to expand, e.g., $A and $B.", &e);
    println!("{}", value);
}

fn expand_env(value: &str, env_user: &EnvMap) -> String {
    let mut expanded = value.to_string();
    for (env_key, env_value) in env_user {
        expanded = expanded.replace(&env_key, &env_value);
    }
    expanded.to_string()
}

I'm not sure why the original error was talking about a closure, but you can fix your code by removing unneeded &:

expanded = expanded.replace(env_key, env_value);

By default, when you iterate over a collection, the values you get are references. So here, env_key and env_value have a type &String. You can pass them to replace as is.

Note that your code may not behave as you expect, in the case where an env_value contains itself a $ variable. They will be partially expanded.

Thanks, that solved the problem.
I did find the error message very misleading (which is unusual for rust).
I note your point, but I will specify that env_value's must not contain env_keys.

About the original error: String::replace expects a Pattern as its first argument, which is a way to accept different kinds of "what to search for" arguments. Pattern is implemented for char, &str, &String, and even &&str, to search for one or more characters. It is also implemented for &[char] to search for a set of chars, and for FnMut(char) -> bool, to search for characters given a predicate.

So in your case, you gave the method replace a &&String, and Pattern is not implemented for &&String. It tried to match it with the impl for FnMut(char) -> bool for some reason, and failed with this error.

The first two lines of the errors are indeed misleading, because the compiler followed a match that we don't expect. The third line gives us an hint, though:

note: required because of the requirements on the impl of `std::str::pattern::Pattern<'_>` for `&&std::string::String`

The compiler tells us that it tries to find an implementation of Pattern for &&String.

1 Like

Thanks for that.

Just for completeness, the simple algorithm I used failed if a short key that was a prefix of a long key was seen first, e.g., if $P was expanded before $PX. Here's the updated function which replaces long keys before short ones:

fn expand_env(value: &str, env_user: &EnvMap) -> String {
    let mut pairs = env_user.iter().collect::<Vec<_>>();
    pairs.sort_unstable_by(|p, q| q.0.len().cmp(&p.0.len())); // By key len
    let mut expanded = value.to_string();
    for (env_key, env_value) in pairs {
        expanded = expanded.replace(env_key, env_value);
    }
    expanded.to_string()
}