I'm currently learning rust by writing a project which is a clone of the board game "Puerto Rico".
the Game struct:
I chose the players member to be an array slice because it can only contain 3, 4 or 5 players (to my understanding, this is the perfect use case for an array slice inside a struct, instead of a Vec)
the Game::new function:
Returns a new Game instance. It takes a list of the names of the players and an empty Vec and populates it with the appropriate Player instances then stores it in the Game as an array slice.
The problem is that this method of instantiating the Game struct seems kind of cumbersome and I feel like there is a way around it (as in passing only the names parameter and somehow creating the array slice inside the new function).
So, is there a way to do it? Should I just change the players member to a Vec?
I tried to move the vec!() declaration inside the new function but, of course, it doesn't work becuase it is dropped at the end of the block.
use super::board::Board;
use super::player::Player;
use super::planatation::ResourceType;
#[derive(Debug)]
pub struct Game<'a> {
board: Board,
players: &'a [Player],
governor: &'a Player
}
impl<'a> Game<'a> {
pub fn new(names: &[String], players: &'a mut Vec<Player>) -> Self {
let num_of_players = names.len() as i32;
let board = Board::new(num_of_players);
if num_of_players < 3 || num_of_players > 5 {
panic!("Only 3, 4 or 5 players may play");
}
if num_of_players < 5 {
for (i, name) in names.iter().enumerate() {
if i < 2 {
players.push(Player::new(name.to_string(), ResourceType::Indigo));
} else {
players.push(Player::new(name.to_string(), ResourceType::Corn));
}
}
} else { // num_of_player == 5
for (i, name) in names.iter().enumerate() {
if i < 3 {
players.push(Player::new(name.to_string(), ResourceType::Indigo));
} else {
players.push(Player::new(name.to_string(), ResourceType::Corn));
}
}
}
Game {
board: board,
players: players,
governor: &players[0]
}
}
}
As you already noticed, a slice does not own its data, it only references them. This is why you must create the players outside of the struct and pass them to the game struct. If you want your struct to hold the players, they must be a Vec instead of a slice.
If governor was not a member of the struct, I would suggest simply using a Vec (or ArrayVec) and be done with it. This, however would mean that governor cannot (generally) be a reference to this very same vector (see Why can't I store a value and a reference to that value in the same struct?).
Depending on the exact semantics and use cases of governor and the other, "regular" players, I would do one of the following:
Having a Vec (or ArrayVec) for "regular" players, and an extra field for the governor. This may be cumbersome if you often have to do the same for both "regular" players and the governor, but you could introduce a method that returns an iterator over all the players and the governor.
Having a Vec (or ArrayVec) for all players, and just store the index into the vector for the governor. As a variation, you could enforce that the governor is always the first element of the Vec holding all players.
Doing as you already did. (This, however, will possibly mean that you cannot easily mutate them (because you can only have one mutable reference at a time). You may work around this via interior mutability, but I am not sure if it is worth it)
Only store a Vec<String> holding the player names. From that, you could derive the number of players. If the kind of player is reasonably simple, you may get away with not even storing the players explicitly, but derive the kind of player by its index (which I suppose may be possible because of the way you determine Indigo or Corn). The disadvantage is that you do not have a player around, but maybe you can model the entire game without them.
Related
I want to store structs in HashMap but also reference to same structs inside another struct vector field.
Let's say I have a tree build of two types of structs Item and Relation.
I am storing all the relations in HashMap by their id,
but I also want to fill each item.out_relations vector with mutable references to same Relation structs which are owned by HashMap.
Here is my Item:
pub struct Item<'a> {
pub id: oid::ObjectId,
pub out_relations: Vec<&'a mut Relation>, // <-- want to fill this
}
And when I am iterating over my relations got from DB
I am trying to do smth like this:
let from_id = relation.from_id; //to find related item
item_map // my items loaded here already
.entry(from_id)
.and_modify(|item| {
(*item)
.out_relations.push(
relation_map.try_insert(relation.id, relation).unwrap() // mut ref to relation expected here
)
}
);
For now compiler warns try_insert is unstable feature and points me to this bug
But let's imagine I have this mutable ref to relation owned already by HashMap- is it going to work?
Or this will be again some ref lives not long enough error? What are my options then? Or I better will store just relation id in item out_relations vector rather then refs? And when needed I will take my relation from the hashmap?
This is called shared mutability, and it is forbidden by the borrow checker.
Fortunately Rust offers safe tools to achieve this.
In your case you need to use Rc<RefCell>, so your code would be:
pub struct Item {
pub id: oid::ObjectId,
pub out_relations: Vec<Rc<RefCell<Relation>>>,
}
And when I am iterating over my relations got from DB I am trying to do smth like this:
let relation = Rc::new(RefCell::new(relation));
let from_id = relation.borrow().from_id; // assuming id is a Copy type
item_map // my items loaded here already
.entry(from_id)
.and_modify(|item| {
(*item)
.out_relations.push(
relation_map.try_insert(relation.id, relation.clone()).unwrap() // mut ref to relation expected here
)
}
);
If you want to mutate relation later, you can use .borrow_mut()
relation.borrow_mut().from_id = new_id;
I agree with Oussama Gammoudi's diagnosis, but I'd like to offer alternative solutions.
The issue with reference counting is that it kills performance.
In your vector, can you store the key to the hash map instead? Then, when you need to get the value from the vector, get the key from the vector and fetch the value from the hash map?
Another strategy is to keep a vector of the values, and then store indices into the vector in your hash map and other vector. This Rust Keynote speaker describes the strategy well: https://youtu.be/aKLntZcp27M?t=1787
I am trying to implement a cli blackjack game in Rust. At the end of each round I want to calculate the winning player and move the cards in each player (including the winner)'s hand into the winners won_hand field.
The project structure has a Table struct which holds players and a deck (Vec<Card>). A Player has a hand field containing their current cards, won_cards which contains all the cards they have won in the game , status, etc.
The logic to calculate the winner is a method on Table. It returns a mutable reference because I want to append to the winner's won_cards field:
/// The current winning player at the table, or None if all players are bust.
pub fn winner(&mut self) -> Option<&mut Player> {
self.players
.iter_mut()
.filter(|p| p.stat != PlayerStat::Bust)
.max_by(|p1, p2| p1.hand_worth().cmp(&p2.hand_worth()))
}
there is also a helper method to collect the cards out of all the players' hands:
pub fn collect_cards(&mut self) -> Vec<Card> {
let mut cards = vec![];
for player in self.players.iter_mut() {
cards.append(&mut player.hand);
}
cards
}
So I could collect the cards and then calculate the winner, which is what I have right now:
let mut drawn_cards = table.collect_cards();
let winner = match table.winner() {
Some(w) => w,
None => {
println!("All players are bust!");
println!("All cards will be reshuffled into the deck.");
table.deck.append(&mut drawn_cards);
table.deck.shuffle(&mut thread_rng());
wait_for_enter();
continue;
}
};
println!("The winner is... {}!", winner.name);
winner.won_cards.append(&mut drawn_cards);
But the problem with that is when I call winner() all the player's hands have already been emptied by collect_cards, meaning the result will be wrong.
I can't call winner before collect_cards because then to use the mutable reference to append the drawn cards to the winner's hand I'd have multiple mutable references at once.
I'm stuck as to how to work around this, is it even possible to do cleanly or would I have to do some restructuring first?
To illustrate the necessity of Rc<T>, the Book presents the following snippet (spoiler: it won't compile) to show that we cannot enable multiple ownership without Rc<T>.
enum List {
Cons(i32, Box<List>),
Nil,
}
use crate::List::{Cons, Nil};
fn main() {
let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
let b = Cons(3, Box::new(a));
let c = Cons(4, Box::new(a));
}
It then claims (emphasis mine)
We could change the definition of Cons to hold references instead, but then we would have to specify lifetime parameters. By specifying lifetime parameters, we would be specifying that every element in the list will live at least as long as the entire list. The borrow checker wouldn’t let us compile let a = Cons(10, &Nil); for example, because the temporary Nil value would be dropped before a could take a reference to it.
Well, not quite. The following snippet compiles under rustc 1.52.1
enum List<'a> {
Cons(i32, &'a List<'a>),
Nil,
}
use crate::List::{Cons, Nil};
fn main() {
let a = Cons(5, &Cons(10, &Nil));
let b = Cons(3, &a);
let c = Cons(4, &a);
}
Note that by taking a reference, we no longer need a Box<T> indirection to hold the nested List. Furthermore, I can point both b and c to a, which gives a multiple conceptual owners (which are actually borrowers).
Question: why do we need Rc<T> when immutable references can do the job?
With "ordinary" borrows you can very roughly think of a statically proven order-by-relationship, where the compiler needs to prove that the owner of something always comes to life before any borrows and always dies after all borrows died (a owns String, it comes to life before b which borrows a, then b dies, then a dies; valid). For a lot of use-cases, this can be done, which is Rust's insight to make the borrow-system practical.
There are cases where this can't be done statically. In the example you've given, you're sort of cheating, because all borrows have a 'static-lifetime; and 'static items can be "ordered" before or after anything out to infinity because of that - so there actually is no constraint in the first place. The example becomes much more complex when you take different lifetimes (many List<'a>, List<'b>, etc.) into account. This issue will become apparent when you try to pass values into functions and those functions try to add items. This is because values created inside functions will die after leaving their scope (i.e. when the enclosing function returns), so we cannot keep a reference to them afterwards, or there will be dangling references.
Rc comes in when one can't prove statically who is the original owner, whose lifetime starts before any other and ends after any other(!). A classic example is a graph structure derived from user input, where multiple nodes can refer to one other node. They need to form a "born after, dies before" relationship with the node they are referencing at runtime, to guarantee that they never reference invalid data. The Rc is a very simple solution to that because a simple counter can represent these relationships. As long as the counter is not zero, some "born after, dies before" relationship is still active. The key insight here is that it does not matter in which order the nodes are created and die because any order is valid. Only the points on either end - where the counter gets to 0 - are actually important, any increase or decrease in between is the same (0=+1+1+1-1-1-1=0 is the same as 0=+1+1-1+1-1-1=0) The Rc is destroyed when the counter reaches zero. In the graph example this is when a node is not being referred to any longer. This tells the owner of that Rc (the last node referring) "Oh, it turns out I am the owner of the underlying node - nobody knew! - and I get to destroy it".
Even single-threaded, there are still times the destruction order is determined dynamically, whereas for the borrow checker to work, there must be a determined lifetime tree (stack).
fn run() {
let writer = Rc::new(std::io::sink());
let mut counters = vec![
(7, Rc::clone(&writer)),
(7, writer),
];
while !counters.is_empty() {
let idx = read_counter_index();
counters[idx].0 -= 1;
if counters[idx].0 == 0 {
counters.remove(idx);
}
}
}
fn read_counter_index() -> usize {
unimplemented!()
}
As you can see in this example, the order of destruction is determined by user input.
Another reason to use smart pointers is simplicity. The borrow checker does incur some code complexity. For example, using smart pointer, you are able to maneuver around the self-referential struct problem with a tiny overhead.
struct SelfRefButDynamic {
a: Rc<u32>,
b: Rc<u32>,
}
impl SelfRefButDynamic {
pub fn new() -> Self {
let a = Rc::new(0);
let b = Rc::clone(&a);
Self { a, b }
}
}
This is not possible with static (compile-time) references:
struct WontDo {
a: u32,
b: &u32,
}
I am currently learning Rust for fun. I have some experience in C / C++ and other experience in other programming languages that use more complex paradigms like generics.
Background
For my first project (after the tutorial), I wanted to create a N-Dimensional array (or Matrix) data structure to practice development in Rust.
Here is what I have so far for my Matrix struct and a basic fill and new initializations.
Forgive the absent bound checking and parameter testing
pub struct Matrix<'a, T> {
data: Vec<Option<T>>,
dimensions: &'a [usize],
}
impl<'a, T: Clone> Matrix<'a, T> {
pub fn fill(dimensions: &'a [usize], fill: T) -> Matrix<'a, T> {
let mut total = if dimensions.len() > 0 { 1 } else { 0 };
for dim in dimensions.iter() {
total *= dim;
}
Matrix {
data: vec![Some(fill); total],
dimensions: dimensions,
}
}
pub fn new(dimensions: &'a [usize]) -> Matrix<'a, T> {
...
Matrix {
data: vec![None; total],
dimensions: dimensions,
}
}
}
I wanted the ability to create an "empty" N-Dimensional array using the New fn. I thought using the Option enum would be the best way to accomplish this, as I can fill the N-Dimensional with None and it would allocate space for this T generic automatically.
So then it comes down to being able to set the entries for this. I found the IndexMut and Index traits that looked like I could do something like m[&[2, 3]] = 23. Since the logic is similar to each other here is the IndexMut impl for Matrix.
impl<'a, T> ops::IndexMut<&[usize]> for Matrix<'a, T> {
fn index_mut(&mut self, indices: &[usize]) -> &mut Self::Output {
match self.data[get_matrix_index(self.dimensions, indices)].as_mut() {
Some(x) => x,
None => {
NOT SURE WHAT TO DO HERE.
}
}
}
}
Ideally what would happen is that the value (if there) would be changed i.e.
let mut mat = Matrix::fill(&[4, 4], 0)
mat[&[2, 3]] = 23
This would set the value from 0 to 23 (which the above fn does via returning &mut x from Some(x)). But I also want None to set the value i.e.
let mut mat = Matrix::new(&[4, 4])
mat[&[2, 3]] = 23
Question
Finally, is there a way to make m[&[2,3]] = 23 possible with what the Vec struct requires to allocate the memory? If not what should I change and how can I still have an array with "empty" spots. Open to any suggestions as I am trying to learn. :)
Final Thoughts
Through my research, the Vec struct impls I see that the type T is typed and has to be Sized. This could be useful as to allocate the Vec with the appropriate size via vec![pointer of T that is null but of size of T; total]. But I am unsure of how to do this.
So there are a few ways to make this more similar to idiomatic rust, but first, let's look at why the none branch doesn't make sense.
So the Output type for IndexMut I'm going to assume is &mut T as you don't show the index definition but I feel safe in that assumption. The type &mut T means a mutable reference to an initialized T, unlike pointers in C/C++ where they can point to initialized or uninitialized memory. What this means is that you have to return an initialized T which the none branch cannot because there is no initialized value. This leads to the first of the more idiomatic ways.
Return an Option<T>
The easiest way would be to change Index::Output to be an Option<T>. This is better because the user can decide what to do if there was no value there before and is close to what you are actually storing. Then you can also remove the panic in your index method and allow the caller to choose what to do if there is no value. At this point, I think you can go a little further with gentrifying the structure in the next option.
Store a T directly
This method allows the caller to directly change what the type is that's stored rather than wrapping it in an option. This cleans up most of your index code nicely as you just have to access what's already stored. The main problem is now initialization, how do you represent uninitialized values? You were correct that option is the best way to do this1, but now the caller can decide to have this optional initialization capability by storing an Option themselves. So that means we can always store initialized Ts without losing functionality. This only really changes your new function to instead not fill with None values. My suggestion here is to make a bound T: Default for the new function2:
impl<'a, T: Default> Matrix<'a, T> {
pub fn new(dimensions: &'a [usize]) -> Matrix<'a, T> {
Matrix {
data: (0..total).into_iter().map(|_|Default::default()).collect(),
dimensions: dimensions,
}
}
}
This method is much more common in the rust world and allows the caller to choose whether to allow for uninitialized values. Option<T> also implements default for all T and returns None So the functionality is very similar to what you have currently.
Aditional Info
As you're new to rust there are a few comments that I can make about traps that I've fallen into before. To start your struct contains a reference to the dimensions with a lifetime. What this means is that your structs cannot exist longer than the dimension object that created them. This hasn't caused you a problem so far as all you've been passing is statically created dimensions, dimensions that are typed into the code and stored in static memory. This gives your object a lifetime of 'static, but this won't occur if you use dynamic dimensions.
How else can you store these dimensions so that your object always has a 'static lifetime (same as no lifetime)? Since you want an N-dimensional array stack allocation is out of the question since stack arrays must be deterministic at compile time (otherwise known as const in rust). This means you have to use the heap. This leaves two real options Box<[usize]> or Vec<usize>. Box is just another way of saying this is on the heap and adds Sized to values that are ?Sized. Vec is a little more self-explanatory and adds the ability to be resized at the cost of a little overhead. Either would allow your matrix object to always have a 'static lifetime.
1. The other way to represent this without Option<T>'s discriminate is MaybeUninit<T> which is unsafe territory. This allows you to have a chunk of initialized memory big enough to hold a T and then assume it's initialized unsafely. This can cause a lot of problems and is usually not worth it as Option is already heavily optimized in that if it stores a type with a pointer it uses compiler magic to store the discriminate in whether or not that value is a null pointer.
2. The reason this section doesn't just use vec![Default::default(); total] is that this requires T: Clone as the way this macro works the first part is called once and cloned until there are enough values. This is an extra requirement that we don't need to have so the interface is smoother without it.
This question already has answers here:
Why can't I store a value and a reference to that value in the same struct?
(4 answers)
How can I store a Chars iterator in the same struct as the String it is iterating on?
(2 answers)
Closed 3 years ago.
I am trying to construct a Defiler struct that owns a fixed sequence and an internal state describing current position within this sequence. The idea is that it can be iterated upon, but sometimes be reset to the beginning of the sequence.
I have picked an iterator as the internal state to represent the current value of the sequence. The idea is that it would be dropped and replaced by a fresh iterator when it's time to reset.
Here is my least fancy naive try so far:
use std::slice::IterMut;
struct Defiler<'a> {
// This sequence should not change during the whole
// lifetime of the struct, although I have not expressed it yet.
seq: Vec<u32>,
// Internal state pointing towards the next element to be
// yielded. It will be randomly replaced by a fresh one.
current: IterMut<'a, u32>,
// ...
}
impl<'a> Defiler<'a> {
fn new(mut seq: Vec<u32>) -> Self {
Defiler {
seq,
current: seq.iter_mut(), // move out of borrowed value, of course.
}
}
// Restart the iteration from scratch.
fn reset(&'a mut self) {
self.current = self.seq.iter_mut();
}
// ...
}
I understand why the above does not compile, and I am aware that I have not expressed many things I wish the compiler to be reassured about:
even though its inner values are mutable, seq shall not grow or shrink during the whole lifetime of Defiler.
current will only iterate on owned values in seq, ever.
=> no dangling iteration can be produced, I'm (quite) sure.
How could I write it?