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
.
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()
}