Marko Seidenglanz
Marko Seidenglanz

Reputation: 137

What is the correct way to pass references?

I have a function that does multiple operations on two vectors. The vectors consist of structs that are later converted to tuples etc....

What I would like is to change the value of a struct in vector 1 by reference, so that the value in vector 1 is equal to the value in vector 2.

When you run the program, you will better see, what I mean. You'll get the following two output lines:

new_rows after: IndexSet { rows: [IndexRow { is_old_idx: false, hash: "1", value: [], name: "", viewentry_id: "yyy" }] }

old_rows after: IndexSet { rows: [IndexRow { is_old_idx: true, hash: "1", value: [], name: "", viewentry_id: "xxx" }] }

And what I would like is that in new_rows_after.rows.viewentry_id there is also an "xxx". But it still contains the original value "yyy".

At some point I don't seem to pass the reference correctly, but I just can't find the place.

Is there perhaps an experienced Rust expert here who can see where the error might be?

Thanks for your help.

Playground link

Upvotes: 0

Views: 223

Answers (1)

Cryptjar
Cryptjar

Reputation: 1129

If I may say that, your code is pretty messy. I mean, if you want us to help you, you could at least try to make it easier for us to help. One thing that generally helps is: try to reduce the length of your example code. Maybe this can help you to come up with a minimal version of your code in the future: http://blog.pnkfx.org/blog/2019/11/18/rust-bug-minimization-patterns/ (it is actually about minimal code examples for ICE, but minimizing Rust examples is really a general thing, if you ask me).


Problem

Now, to the actual problem. I don't really understand your full example code, so what I did, I just reduce it from a whooping 194 lines down to 43 lines, just keeping, what I assume, is your actual problem.

Also, I found your variable names rather confusing. I mean, what I understand that your problem is about, is that new should have the value from old but instead new has its old value instead of the new value of old -- seriously, that is just confusing. So, I went with a simple foo and bar, and here we go:

fn main() {
    let dummy_foo = vec![IndexRow {
        id: "good".to_string(),
    }];
    let dummy_bar = vec![IndexRow {
        id: "bad".to_string(),
    }];
    let set_foo = IndexSet { rows: dummy_foo };
    let mut set_bar = IndexSet { rows: dummy_bar };

    // Should copy ids from set_foo to set_bar
    copy_ids(&mut set_bar, &set_foo);

    // Here set_bar still contains "bad"
    println!("set_bar: {:?}", set_bar);
}

#[derive(Debug)]
pub struct IndexRow {
    pub id: String,
}

#[derive(Debug)]
pub struct IndexSet {
    pub rows: Vec<IndexRow>,
}

/// Copy ids from `src` to `dest`
pub fn copy_ids<'a, 'b>(dest: &'a mut IndexSet, src: &'b IndexSet) {
    // Create tuples each with a dest and src entry
    let mut tuples: Vec<(&str, &str)> = dest
        .rows
        .iter()
        .zip(src.rows.iter())
        .map(|(d, s)| (d.id.as_str(), s.id.as_str()))
        .collect();

    for t in tuples.iter_mut() {
        let (ref mut dest_id, src_id) = t;
        // Override dest with src
        *dest_id = *src_id;
    }
}

playground

Now as I understand it, in this above version, the issue is just that the id in set_bar should be replaced with the id in set_foo, but instead set_bar still contains the old "bad" as it is printed at the end of main.

Solution

Assuming that this in deed the case: the problem is rather simple. You need to actually change the id, which is a String. However, in the tuples variable, you have only immutable (&) strs. Therefore, *dest_id = *src_id just replaces the one reference with another and all that is only stored/modified within tuples. The actual String is never touched, it is not even accessible as such from tuples.

So, what you need to do is: get your self access to a modifiable (&mut) String and then modify that string directly. Here you can either replace the entire string e.g. with *dest_id = src_id.to_string(), or if you want to make sure that you really have a String on the left-hand side, you can call a function on it that only exists on String and not on str like dest_id.replace_range(.., src_id).

So, this version of copy_ids does what it should do:

/// Copy ids from `src` to `dest`
pub fn copy_ids<'a, 'b>(dest: &'a mut IndexSet, src: &'b IndexSet) {
    // Create tuples each with a dest and src entry
    let tuples: Vec<(&mut String, &str)> = dest
        .rows
        .iter_mut()
        .zip(src.rows.iter())
        .map(|(d, s)| (&mut d.id, s.id.as_str()))
        .collect();

    // Override dest with src
    for (dest_id, src_id) in tuples.into_iter() {
        // Replace the content of the String
        dest_id.replace_range(.., src_id);
    }
}

full example on playground

Upvotes: 1

Related Questions