In general, what you are trying to accomplish is unsafe, and Rust correctly prevents you from doing something you shouldn't. For a simple example, consider a Vec<u8> . If a vector has one element and unit capacity, adding another value to the vector will redistribute and copy all the values ββin the vector, which will invalidate any links in the vector. This will cause all your keys in your index to point to arbitrary memory addresses, which will lead to unsafe behavior. The compiler prevents this.
In this case, there are two additional pieces of information that the compiler does not know, but the programmer does not:
- An additional indirect notation is added there -
String , so moving the pointer to this heap distribution is not a problem. String will never be changed. If this were so, then he could redistribute by canceling the address mentioned.
In such cases, it is normal to use unsafe code if you correctly document why it is unsafe .
use std::collections::HashMap; use std::mem; #[derive(Debug)] struct Player { name: String, } fn main() { let names = ["alice", "bob", "clarice", "danny", "eustice", "frank"]; let mut players = Vec::new(); let mut index = HashMap::new(); for &name in &names { let player = Player { name: name.into() }; let idx = players.len();
However, I assume that you do not want to use this code in your main method, but want to return it from some function. This will be a problem since you will quickly come across Why can't I keep the value and the link to that value in the same structure? .
Honestly, there are code styles that don't fit into Rustβs limitations. If you come across this, you can:
- Decide that Rust is not suitable for you or your problem.
- use
unsafe code, preferably thoroughly tested and only exposing a secure API. - Explore alternative views.
For example, I would probably rewrite the code so that the index is the primary owner of the key:
use std::collections::BTreeMap; #[derive(Debug)] struct Player<'a> { name: &'a str, data: &'a PlayerData, } #[derive(Debug)] struct PlayerData { hit_points: u8, } #[derive(Debug)] struct Players(BTreeMap<String, PlayerData>); impl Players { fn new<I, S>(iter: I) -> Self where I: IntoIterator<Item = S>, S: AsRef<str>, { let players = iter.into_iter() .map(|name| (name.as_ref().to_string(), PlayerData { hit_points: 100 })) .collect(); Players(players) } fn get<'a>(&'a self, name: &'a str) -> Option<Player<'a>> { self.0.get(name).map(|data| { Player { name: name, data: data, } }) } } fn main() { let names = ["alice", "bob", "clarice", "danny", "eustice", "frank"]; let players = Players::new(&names); for (k, v) in &players.0 { println!("{:?} -> {:?}", k, v); } println!("{:?}", players.get("eustice")); }
Alternatively, as shown in What is an idiomatic way to make a lookup table that uses an element field as a key? , you can wrap your type and save it in a container with a set:
use std::collections::BTreeSet; #[derive(Debug, PartialEq, Eq)] struct Player { name: String, hit_points: u8, } #[derive(Debug, Eq)] struct PlayerByName(Player); impl PlayerByName { fn key(&self) -> &str { &self.0.name } } impl PartialOrd for PlayerByName { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { Some(self.cmp(other)) } } impl Ord for PlayerByName { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.key().cmp(&other.key()) } } impl PartialEq for PlayerByName { fn eq(&self, other: &Self) -> bool { self.key() == other.key() } } impl std::borrow::Borrow<str> for PlayerByName { fn borrow(&self) -> &str { self.key() } } #[derive(Debug)] struct Players(BTreeSet<PlayerByName>); impl Players { fn new<I, S>(iter: I) -> Self where I: IntoIterator<Item = S>, S: AsRef<str>, { let players = iter.into_iter() .map(|name| PlayerByName(Player { name: name.as_ref().to_string(), hit_points: 100 })) .collect(); Players(players) } fn get(&self, name: &str) -> Option<&Player> { self.0.get(name).map(|pbn| &pbn.0) } } fn main() { let names = ["alice", "bob", "clarice", "danny", "eustice", "frank"]; let players = Players::new(&names); for player in &players.0 { println!("{:?}", player.0); } println!("{:?}", players.get("eustice")); }
do not increase runtime with Rc or RefCell
Guessing performance characteristics without profiling is never a good idea. I honestly donβt think that there will be a noticeable performance loss from incrementing an integer when cloning or deleting a value. If this problem requires both an index and a vector, then I would achieve some sort of sharing.