Mutating parts of a mutable structure

Mutating values held in a Hashmap.
I have a structure RoloGuiState. Which has a

rolodex: Option<RolodexBase>.

RolodexBase has a bunch of content. For now, the one that matters is a Vec.

    mut guistate: &mut RoloGuiState,
    message: CategoryMessage,
) -> iced::Task<Message> {
...
if let Some(dex) = &mut guistate.rolodex {
                guistate.database_dirty = true;
                match cat {
                    CategoryType::Short => dex.del_short_cat(instr),

Which results in the error:

dex` is a `&` reference, so it cannot be borrowed as mutable

This worked when I was using RefCell / Rc. So how do I mutate the vectors without the RefCell / Rc?
For completeness, when I had RefCell / Rc, I used just dex rather than (*dex), and I had a clone, which for a an Rc I knew wouldn't mess anything up. And I had tested it and it worked.
Thanks,
Joel

I think some of your mid-sentence types got truncated because they looked like Option<HtmlTagLookingThing>.

Wrap mid-sentence code in single backticks to fix this.

[...] the one that matters is a `Vec<Whatever>`.
```
    mut guistate: &mut RoloGuiState,
    [...]
```

Didn't realize that would get mangled. COrrected.
Joel

I'd expect the given code to work, so I think we're missing some relevant code.

Ill post more, in hopes it helps...

#[derive(Debug, Clone)]
// Base structure (object) for the rolodex.
// This owns all people (and, once implemented, organizations)
// it also holds various lists of labels used in people, etc.
pub struct RolodexBase {
    // if non-empty, the name of the file used to build the rolodex
    name: String,
    // All the people stored in the rolodex
    max_person_id: usize,
    people: HashMap<usize, Person>,
    // categories of relatively short string annotations
    shortcategories: Vec<String>,
    // categories of relatively long string annotations
    longcategories: Vec<String>,
    // labels usable with each category
    categorylabels: HashMap<String, Vec<String>>,
    // labels for use with associations between people
    assoclabels: Vec<String>,
}
...
    // delete s short category name, and all its sub-labels
    pub fn del_short_cat(&mut self, name: String) {
        let mut found = false;
        let mut index = 0;
        for val in self.shortcategories.iter() {
            if *val == name {
                found = true;
            } else if !found {
                index += 1;
            }
        }
        if found {
            self.shortcategories.remove(index);
            if self.categorylabels.contains_key(&name) {
                self.categorylabels.remove(&name);
            }
        }
    }
....
pub struct RoloGuiState {
    rolodex: Option<RolodexBase>,
    main_state: MainPanelGuiState,
    category: category_gui::CategoryState,
    people: person_gui::PeopleGuiState,
    database_dirty: bool,
    mainpanel_dirty: bool,
}
....
impl RoloGuiState {
....
    fn update(&mut self, message: Message) -> iced::Task<Message> {
        match message {
....
            Message::CategoryMessages(mess) => {
                return (category_gui::category_update(self, mess));
            }
....
pub fn category_update(
    mut guistate: &mut RoloGuiState,
    message: CategoryMessage,
) -> iced::Task<Message> {
    match message {
        CategoryMessage::DelCategoryPressed(cat, instr) => {
            if let Some(dex) = &mut guistate.rolodex {
                guistate.database_dirty = true;
                match cat {
                    CategoryType::Short => dex.del_short_cat(instr),
                    CategoryType::Long => dex.del_long_cat(instr),
                    CategoryType::Assoc => dex.del_assoc_label(instr),
                    CategoryType::Subsidiary => {
                        if !guistate.category.subcat_selection.is_empty() {
                            dex.del_cat_label(guistate.category.subcat_selection.clone(), instr);
                        }
                    }
                }
            }
        }
```
If there are things I missed, jsut ask and I will ad them.  
Yours,
Joel

No luck yet. What's the complete output of cargo check in a console?

The first part of the compiler output:

-*- mode: compilation; default-directory: "c:/Users/jmh/OneDrive/Documents/rust projects/rolodex/" -*-
Compilation started at Sun Mar  1 17:39:11

cargo build 
   Compiling rolodex v0.1.0 (C:\Users\jmh\OneDrive\Documents\rust projects\rolodex)
error[E0596]: cannot borrow `*dex` as mutable, as it is behind a `&` reference
  --> src\rolo_window\category_gui.rs:89:25
   |
89 |                         (*dex).add_short_cat(guistate.category.short_entry.clone());
   |                         ^^^^^^ `dex` is a `&` reference, so it cannot be borrowed as mutable

error[E0596]: cannot borrow `*dex` as mutable, as it is behind a `&` reference
  --> src\rolo_window\category_gui.rs:93:25
   |
93 |                         (*dex).add_long_cat(guistate.category.long_entry.clone());
   |                         ^^^^^^ `dex` is a `&` reference, so it cannot be borrowed as mutable

error[E0596]: cannot borrow `*dex` as mutable, as it is behind a `&` reference
  --> src\rolo_window\category_gui.rs:97:25
   |
97 |                         (*dex).add_assoc_label(guistate.category.assoc_entry.clone());
   |                         ^^^^^^ `dex` is a `&` reference, so it cannot be borrowed as mutable

error[E0596]: cannot borrow `*dex` as mutable, as it is behind a `&` reference
   --> src\rolo_window\category_gui.rs:101:29
    |
101 | ...                   (*dex).add_cat_label(
    |                       ^^^^^^ `dex` is a `&` reference, so it cannot be borrowed as mutable

error[E0507]: cannot move out of `guistate.rolodex` which is behind a shared reference
    --> src\rolo_window\category_gui.rs:120:17
     |
 120 |     let rolo = &guistate.rolodex.unwrap();
     |                 ^^^^^^^^^^^^^^^^ -------- `guistate.rolodex` moved due to this method call
     |                 |
     |                 help: consider calling `.as_ref()` or `.as_mut()` to borrow the type's contents
     |                 move occurs because `guistate.rolodex` has type `Option<RolodexBase>`, which does not implement the `Copy` trait
     |
note: `Option::<T>::unwrap` takes ownership of the receiver `self`, which moves `guistate.rolodex`
    --> C:\Users\jmh\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib/rustlib/src/rust\library\core\src\option.rs:1013:25
     |
1013 |     pub const fn unwrap(self) -> T {
     |                         ^^^^
help: you can `clone` the value and consume it, but this might not be your desired behavior
     |
 120 |     let rolo = &<Option<RolodexBase> as Clone>::clone(&guistate.rolodex).unwrap();
     |                 +++++++++++++++++++++++++++++++++++++++                +
help: consider cloning the value if the performance cost is acceptable
     |
 120 |     let rolo = &guistate.rolodex.clone().unwrap();
     |                                 ++++++++

error[E0507]: cannot move out of `guistate.category.subcat_selectopt` which is behind a shared reference

Yours,
Joel

Looks like the error is in some other part of the code where you're doing .add_long_cat and so on, not .del_long_cat.

1 Like

Interesting. I hadn't noticed that it was complaining about add but not delete.

    // add a short category string, and create an empty set of sub-labels
    pub fn add_short_cat(&mut self, name: String) {
        if !name.is_empty()
            && !self.shortcategories.contains(&name)
            && !self.categorylabels.contains_key(&name)
        {
            self.shortcategories.push(name.clone());
            self.categorylabels.insert(name.clone(), vec![]);
        }
    }

        CategoryMessage::AddCategoryEntry(cat) => {
            if let Some(dex) = &guistate.rolodex {
                guistate.database_dirty = true;
                match cat {
                    CategoryType::Short => {
                        (*dex).add_short_cat(guistate.category.short_entry.clone());
                        guistate.category.short_entry = "".to_string();
                    }
                    CategoryType::Long => {
                        guistate.category.long_entry = "".to_string();    
                        (*dex).add_long_cat(guistate.category.long_entry.clone());
                    }
                    CategoryType::Assoc => {
                        (*dex).add_assoc_label(guistate.category.assoc_entry.clone());
                         guistate.category.assoc_entry = "".to_string();
                   }

I presume there is some difference in what I wrote that I am not seeing. Help appreciated and sorry to be dense.
Joel

- if let Some(dex) = &guistate.rolodex {
+ if let Some(dex) = &mut guistate.rolodex {
1 Like

Thanks. That did it. Joel

By the way, you can get rid of those (*dex) and replace them with just dex. Explicitly dereferencing (or referencing) is very rarely necessary for a method call — it only happens when there is a same-named method for the reference you're dereferencing, such as if the reference and the referent both implement the same trait and you need the inner one.

1 Like

Thanks. I put them in to try to solve the problem. Joel