Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

struct with reference to element of a vector in another field

I have the below example where I want a struct which holds a vector of data in one field, and has another field which contains the currently selected field. My understanding is that this is not possible in rust because I could remove the element in tables which selected points to, thereby creating a dangling pointer (can't borrow mut when an immutable borrow exists). The obvious workaround is to instead store a usize index for the element, rather than a &'a String. But this means I need to update the index if I remove an element from tables. Is there any way to avoid this using smart pointers, or just any better solutions in general? I've looked at other questions but they are not quite the same as below, and have extra information which makes them harder to follow for a beginner like myself, whereas below is a very minimal example.

struct Data<'a> {
    selected: &'a String,
    tables: Vec<String>,
}

fn main() {
    let tables = vec!["table1".to_string(), "table2".to_string()];
    let my_stuff = Data {
        selected: &tables[0],
        tables: tables,
    };
}
like image 748
Max888 Avatar asked Sep 20 '25 08:09

Max888


1 Answers

You quite rightfully assessed that the way you wrote it is not possible, because Rust guarantees memory safety and storing it as a reference would give the possibility to create a dangling pointer.

There are several solutions that I could see here.


Static Strings

This of course only works if you store compile-time static strings.

struct Data {
    selected: &'static str,
    tables: Vec<&'static str>,
}

fn main() {
    let tables = vec!["table1", "table2"];
    let my_stuff = Data {
        selected: &tables[0],
        tables,
    };
}

The reason this works is because static strings are non-mutable and guaranteed to never be deallocated. Also, in case this is confusing, I recommend reading up on the differences between Strings and str slices.

You can even go one further and reduce the lifetime down to 'a. But then, you have to store them as &'a str in the vector, to ensure they cannot be edited. But that then allows you to store Strings in them, as long as the strings can be borrowed for the entire lifetime of the Data object.

struct Data<'a> {
    selected: &'a str,
    tables: Vec<&'a str>,
}

fn main() {
    let str1 = "table1".to_string();
    let str2 = "table2".to_string();
    let tables = vec![str1.as_str(), str2.as_str()];

    let my_stuff = Data {
        selected: &tables[0],
        tables,
    };
}

Reference counting smart pointers

Depending your situation, there are several types that are recommended:

  • Rc<...> - if your data is immutable. Otherwise, you need to create interior mutability with:
  • Rc<Cell<...>> - safest and best solution IF your problem is single-threaded and deals with simple data types
  • Rc<RefCell<...>> - for more complex data types that have to be updated in-place and can't just be moved in and out
  • Arc<Mutex<...>> - as soon as your problem stretches over multiple threads

In your case, the data is in fact simple and your program is single-threaded, so I'd go with:

use std::{cell::Cell, rc::Rc};

struct Data {
    selected: Rc<Cell<String>>,
    tables: Vec<Rc<Cell<String>>>,
}

fn main() {
    let tables = vec![
        Rc::new(Cell::new("table1".to_string())),
        Rc::new(Cell::new("table2".to_string())),
    ];
    let my_stuff = Data {
        selected: tables[0].clone(),
        tables,
    };
}

Of course, if you don't want to modify your strings after creation, you could go with:

use std::rc::Rc;

struct Data {
    selected: Rc<String>,
    tables: Vec<Rc<String>>,
}

fn main() {
    let tables = vec![Rc::new("table1".to_string()), Rc::new("table2".to_string())];
    let my_stuff = Data {
        selected: tables[0].clone(),
        tables,
    };
}

Hiding the data structure and using an index

As you already mentioned, you could use an index instead. Then you would have to hide the vector and provide getters/setters/modifiers to make sure the index is kept in sync when the vector changes.

I'll keep the implementation up to the reader and won't provide an example here :)


I hope this helped already, or at least gave you a couple of new ideas. I'm happy to see new people come to the community, so feel free to ask further questions if you have any :)

like image 82
Finomnis Avatar answered Sep 23 '25 07:09

Finomnis