Store &str in collection owned by same struct

Let's say I have the following struct

pub struct ReplacementSettings<'a> {
    /// A list of all replacement values
    pub replacements: HashMap<String, Vec<String>>,

    /// An internal list of value which are going to be replaces without user input.
    direct_replacements: HashMap<&'a str, Vec<&'a str>>,

    /// An internal list of values that are going to be presented to the user
    search_list: HashSet<&'a str>,
}

The replacements HashMap is filled when the struct is created and can be changed by the user. The direct_replacements and search_list fields are generated by a separate function from the replacements field and cannot be changed by the user directly.

The fill routine roughly goes like this:

pub fn new(path: PathBuf) -> Result<ReplacementSettings<'a>, SettingsError> {
    let mut file = File::open(&path)?;
    let mut buffer = String::new();
    file.read_to_string(&mut buffer)?;

    let mut config: ReplacementSettings = toml::from_str(&buffer)?;
    for (key, terms) in &config.replacements {
        if value.is_empty() {
            config.search_list.insert(key);
        } else {
            for term in terms {
                config.direct_replacements.insert(term, key);
            }
        }
    }
    Ok(config)
}

But when I try to compile I get two errors:
a) [E0505]: cannot move out of config because it is borrowed
b) [E0515]: cannot return value referencing local data config.replacements

and I'm asking myself, if this is even possible. This very much looks like a self-referencing struct, which as far as I know is very hard to pull off and a bit discouraged.

Now I could clone each string and store them in the two fields when they are filled, but the replacements HashMap contains thousands of Strings and there are possibly hundreds of ReplacementSettings this would mean a lot of string allocations. Since direct_replacements and search_list are only ever read by the user I don't see the point in that.

Am I missing something obvious here?

In Rust structs can't borrow any data from themselves, under any circumstances. That's because the borrow checker is unable to verify safety of such structs.

You will have to use Arc<str> for strings that are shared between sets, or refer to them in some other way, like a numeric index. Alternatively, store the strings outside of the struct.

2 Likes

So I'd have to change the struct to something like this:

pub struct ReplacementSettings {
    pub replacements: HashMap<String, Vec<String>>,
    direct_replacements: HashMap<Rc<str>, Vec<Rc<str>>>,
    search_list: HashSet<Rc<str>>,
}

How would that work with interior mutability down the line?

You can't mutate content of Rc<str>, the same way you can't mutate &str.

BTW: replacements should contain Rc<str> too, so that you clone refcount, not the string content.

My bad. I wasn't entirely clear on the interior mutability part. Later down the line the user should be able to set the replacements field (or the contents of the map) via method(s) and in the same turn the direct_replacements and search_list fields are recalculated.
Although now that I think of it -- would I even need interior mutability for that?

No, that shouldn’t require interior mutability. Your method will take an &mut self parameter, and is then free to modify any or all of the fields.

1 Like

I'll add that if you aren't going to modify or free these settings before the program exits, you could alternatively leak memory and either use &'static str or use Intern<String> from the internment crate. Either of these are very easy to use, and allow you to safely and easily handle multiple copies of the strings.

1 Like

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.