I am writing my first program in Rust that takes a list of Cards and attempts to find the best deck from those cards.
I want to have a catalog of Cards that can be copied to decks. I'm trying to find an idiomatic way of doing this. My first thought was a vector or array containing one of every card and a function to return a copy of that card to a deck.
Here is my code:
pub trait Card {
fn get_name(&self) -> &String;
fn get_card_type(&self) -> &CardType;
fn get_cost(&self) -> Option<&i32>;
fn play(&self){
println!("Played {} for {} mana.", self.get_name(), self.get_cost().unwrap());
}
}
pub enum CardType {
Creature,
Spell,
Land,
}
pub struct Creature {
pub name: String,
pub card_type: CardType,
pub cost: i32,
pub attack: i32,
pub defense: i32,
pub tapped: bool,
}
impl Card for Creature{
fn get_name(&self) -> &String {
&self.name
}
fn get_card_type(&self) -> &CardType {
&self.card_type
}
fn get_cost(&self) -> Option<&i32> {
Some(&self.cost)
}
}
pub struct Spell {
pub name: String,
pub card_type: CardType,
pub cost: i32,
pub damage: i32,
}
impl Card for Spell{
fn get_name(&self) -> &String {
&self.name
}
fn get_card_type(&self) -> &CardType {
&self.card_type
}
fn get_cost(&self) -> Option<&i32> {
Some(&self.cost)
}
}
pub struct Land {
pub name: String,
pub card_type: CardType,
pub tapped: bool,
}
impl Card for Land{
fn play(&self) {
println!("Played {}.", self.get_name());
}
fn get_name(&self) -> &String {
&self.name
}
fn get_card_type(&self) -> &CardType {
&self.card_type
}
fn get_cost(&self) -> Option<&i32> {
None
}
}
pub fn get_random_card() -> Box<Card> {
Box::new( Creature{
name: "My Card".to_string(),
card_type: CardType::Creature,
cost: 1,
attack: 2,
defense: 2,
tapped: false,
})
}
The get_random_card() function contains a sample card. So essentially I just need a static array or vector of cards and a function to copy them in to a deck, but I haven't been able to implement it.
Any suggestions? Feel free to point out anything else I am doing wrong.
Edit: Some clarification -
The code here works, but I want a variable containing a list of available cards. For example
// some pseudocode, in file cards.rs
let cards = [
Creature {
name = "Creature 1"
//...
},
Land {
name = "Land 1"
//...
},
Spell {
name = "Spell 1"
//...
},
];
fn get_card(name) -> mut Card {
// return a mutable copy/clone of a card, not a reference
}
And I would prefer to have it declared outside of the main function, in a separate file. I've tried several different things trying to make the compiler happy, but I'm pretty sure I'm missing something obvious. Memory isn't a big concern at the moment, there won't be that many cards in the "cards" var. But decks will be generated dynamically, so I need somewhere to get the cards in the deck from.
Thanks.
If you aren't worried about allocating too much memory, you have everything you need right now:
fn main() {
let hand: Vec<_> = (0..5).map(|_| get_random_card()).collect();
for card in &hand {
println!("{}", card.get_name());
}
}
We simply grab 5 cards and store them in a Vec. We can then iterate over the vector and print out the card names.
If you are worried about memory and you are going to have a bunch of cards you want to "reuse", you could do as above and then take references to them:
fn main() {
let deck: Vec<_> = (0..52).map(|_| get_random_card()).collect();
let hand1 = &deck[0..5];
let hand2 = &deck[5..10];
let hand3 = &deck[10..15];
let hand4 = &deck[15..20];
for card in hand1 {
println!("{}", card.get_name());
}
}
Here, the compiler will prevent you from attempting to use a card after the deck goes out of scope. If you need more flexibility, you could use Rc in addition to Box:
use std::rc::Rc;
pub fn get_random_card() -> Rc<Box<Card>> {
Rc::new(Box::new(Creature {
name: "My Card".to_string(),
card_type: CardType::Creature,
cost: 1,
attack: 2,
defense: 2,
tapped: false,
}))
}
fn main() {
let deck: Vec<_> = (0..52).map(|_| get_random_card()).collect();
let hand1 = deck[0..5].to_owned();
let hand2 = deck[5..10].to_owned();
let hand3 = deck[10..15].to_owned();
let hand4 = deck[15..20].to_owned();
for card in &hand1 {
println!("{}", card.get_name());
}
}
This lets each card manage a reference-count of active references. When the references go to 0, the card is freed.
Note In the Rust nightlies, you can use just Rc<T> instead of Rc<Box<T>>.
Feel free to point out anything else I am doing wrong.
Three things stood out to me:
You should probably use #[derive(Debug)] on every struct. Other things to potentially derive are Copy and/or Clone, PartialEq and Hash. Of course, you can wait until you need one of those before adding it, but Debug is super useful right away.
Return &str instead of &String. 99.9% of the time, you want to use &str instead of &String — it's more flexible.
There's no reason to return references to small types like CardType or i32 - just return them directly.
Related
I want to make a graph with a safe interface:
pub struct VertexId {
id: usize,
}
pub struct Graph {
vertices: Vec<String>,
edges: Vec<(VertexId, VertexId)>,
}
impl Graph {
pub fn add_vertex(&mut self, label: String) -> VertexId {
self.vertices.push(label);
VertexId { id: self.vertices.len() - 1 }
}
pub fn add_edge(&mut self, from: VertexId, to: VertexId) {
self.edges.push((from, to));
}
}
Here, I've created a VertexId wrapper so that you can only get vertex ids from a Graph.
However, it's possible to use invalid VertexIds if you create two Graphs:
let mut a = Graph::new();
let vid = a.add_vertex("hello".to_string());
let mut b = Graph::new();
b.add_edge(vid, vid);
Is it possible to link vid to a at compile time?
Note: If you decide to use the following approach, you'll discover that it brings serious downsides, and - in practice - is probably not worth it.
You could make each graph instance an own type. One way to do so is to equip it with an identifier, and require that the vertices' identifier and the graph's identifier match.
I.e. you could e.g. do this (I chose the IDENTIFIER to be a const u32, but you could also use a type):
pub struct VertexId<const IDENTIFIER: u32> {
id: usize,
}
pub struct Graph<const IDENTIFIER: u32> {
vertices: Vec<String>,
edges: Vec<(VertexId<IDENTIFIER>, VertexId<IDENTIFIER>)>,
}
impl<const IDENTIFIER: u32> Graph<IDENTIFIER> {
pub fn add_vertex(&mut self, label: String) -> VertexId<IDENTIFIER> {
self.vertices.push(label);
VertexId {
id: self.vertices.len() - 1,
}
}
pub fn add_edge(&mut self, from: VertexId<IDENTIFIER>, to: VertexId<IDENTIFIER>) {
self.edges.push((from, to));
}
}
Then, each time you construct a graph, you'd have to supply an identifier. This becomes annoying pretty soon, so you might define this helper macro that infers the identifier from the line number:
macro_rules! make_graph {
() => {{
const LINE: u32 = line!();
Graph::<LINE> {
vertices: Vec::new(),
edges: Vec::new(),
}
}};
}
Then, you would not be able to add_edge vertices from a to another graph b:
let mut a = make_graph!();
let vid = a.add_vertex("hello".to_string());
let mut b = make_graph!();
// b.add_edge(vid, vid); // does not compile
Note that make_graph could lead to the same type in two different files, but with matching line numbers. This also highlights one drawback of this: As soon as you move the make_graph to another line, you get another type. You could get around this by using types as identifiers, but then you'd have to declare identifier types over and over. tl;dr; Just because you can enforce something in the type system, it is not necessarily a good idea.
You can use branded lifetimes. generativity implements those, but you can also do it yourself by looking how it does that.
The idea is explained well in generativity's README. The basic idea is that we make each Graph instance its own type via a lifetime, then each graph instance can have its own vertices as different types.
pub struct Graph<'id> {
id: generativity::Id<'id>,
vertices: Vec<String>,
edges: Vec<(VertexId<'id>, VertexId<'id>)>,
}
impl<'id> Graph<'id> {
pub fn new(guard: generativity::Guard<'id>) -> Self {
Self {
id: guard.into(),
vertices: Vec::new(),
edges: Vec::new(),
}
}
pub fn add_vertex(&mut self, label: String) -> VertexId<'id> {
self.vertices.push(label);
VertexId {
_id: self.id,
index: self.vertices.len() - 1,
}
}
pub fn add_edge(&mut self, from: VertexId<'id>, to: VertexId<'id>) {
self.edges.push((from, to));
}
}
#[derive(Clone, Copy)]
pub struct VertexId<'id> {
_id: generativity::Id<'id>,
index: usize,
}
fn main() {
generativity::make_guard!(graph);
let mut graph = Graph::new(graph);
let vertex = graph.add_vertex("Foo".to_owned());
dbg!(graph.get_vertex_label(vertex));
generativity::make_guard!(graph2);
let graph2 = Graph::new(graph2);
// Does not compile.
// graph2.get_vertex_label(vertex);
}
The advantages of this approach over #phimuemue's approach of using a non-lifetime generic parameters are:
This monomorphizes only once, not once per instance, reducing compile time size and code bloat.
Because the lifetime is guaranteed to be unique, you can rely on it for soundness. For example, you can use get_unchecked() for getting nodes:
impl<'id> Graph<'id> {
pub fn get_vertex_label(&self, vertex: VertexId<'id>) -> &str {
// SAFETY: The lifetime verifies this come from the same graph, and we never remove vertices.
unsafe { self.vertices.get_unchecked(vertex.index) }
}
}
Here's an example of a problem I ran into:
pub struct Item {
name: String,
value: LockableValue, // another struct that I'd like to mutate
}
impl Item {
pub fn name(&self) -> &str {
&self.name
}
pub fn value_mut(&mut self) -> &mut LockableValue {
&self.value
}
}
pub fn update(item: &mut Item) {
let value = item.value_mut();
value.change(); // how it changes is unimportant
println!("Updated item: {}", item.name());
}
Now, I know why this fails. I have a mutable reference to item through the mutable reference to the value.
If I convert the reference to an owned String, it works fine, but looks strange to me:
pub fn update(item: &mut Item) {
let name = { item.name().to_owned() };
let value = item.value_mut();
value.change(); // how it changes is unimportant
println!("Updated item: {}", name); // It works!
}
If I let value reference drop, then everything is fine.
pub fn update(item: &mut Item) {
{
let value = item.value_mut();
value.change(); // how it changes is unimportant
}
println!("Updated item: {}", item.name()); // It works!
}
The value.change() block is rather large, and accessing other fields in item might be helpful. So while I do have solutions to this issue, I'm wondering if there is a better (code-smell) way to do this. Any suggestions?
My intention behind the above structs was to allow Items to change values, but the name should be immutable. LockableValue is an tool to interface with another memory system, and copying/cloning the struct is not a good idea, as the memory is managed there. (I implement Drop on LockableValue to clean up.)
I was hoping it would be straight-forward to protect members of the struct from modification (even if it were immutable) like this... and I can, but it ends up looking weird to me. Maybe I just need to get used to it?
You could use interior mutability on only the part that you want to mutate by using a RefCell like ths:
use std::cell::{RefCell, RefMut};
pub struct LockableValue;
impl LockableValue {
fn change(&mut self) {}
}
pub struct Item {
name: String,
value: RefCell<LockableValue>, // another struct that I'd like to mutate
}
impl Item {
pub fn name(&self) -> &str {
&self.name
}
pub fn value_mut(&self) -> RefMut<'_, LockableValue> {
self.value.borrow_mut()
}
}
pub fn update(item: &Item) {
let name = item.name();
let mut value = item.value_mut();
value.change(); // how it changes is unimportant
println!("Updated item: {}", name);
}
That way you only need a shared reference to Item and you don't run into an issue with the borrow checker.
Not that this forces the borrow checks on value to be done at runtime though and thus comes with a performance hit.
The question pretty much says it: can I create a boxed tuple (or any struct) that directly contains a slice as one of its fields?
The Vec::into_boxed_slice method works to create Box<[U]>, but I would like to add on some additional data directly in the box alongside the slice.
For example, here is the kind of struct I would like to box up without using Vec into something like Box<(i32,[u8])>:
struct ConsolidateMe {
data: i32,
arr: Vec<u8>,
}
impl ConsolidateMe {
fn new(n: usize) -> Self {
let mut arr = Vec::with_capacity(n);
arr.resize(n, 42);
ConsolidateMe {
data: 15,
arr,
}
}
}
(In my application, I am creating many structs on the heap, each of which contains a small array and a few other values. The arrays have different sizes, but the size doesn't change after the struct is created. I'd like these to be compactly stored and to avoid extra indirection to the extent possible.)
The slice-dst crate looks like a perfect fit.
Example:
use slice_dst::SliceWithHeader;
#[derive(Debug, Clone)]
struct MyType {
inner: Box<SliceWithHeader<String, i32>>,
}
impl MyType {
fn new(value: &str) -> Self {
let values: Vec<i32> = value.split(" ").map(|s| s.trim().parse().unwrap()).collect();
Self {
inner: SliceWithHeader::new(value.into(), values.into_iter()),
}
}
}
fn main() {
println!("{:?}", MyType::new("1 2 3"));
}
I want to create a struct, store a reference to it in a vec, and then use the struct later on:
pub struct NonTerminal {
name: String
}
impl<'a> NonTerminal {
pub fn new(grammar: &'a mut Grammar, name: &str) -> &'a NonTerminal {
let t = NonTerminal {
name: name.to_string()
};
grammar.non_terminals.push(t);
grammar.non_terminals.last().unwrap()
}
}
pub struct Grammar<'a> {
non_terminals: Vec<NonTerminal>
}
pub fn create_rules<'a>(g: &'a mut grammar::Grammar<'a>) {
let sum = grammar::NonTerminal::new(g, "Sum");
let product = grammar::NonTerminal::new(g, "Product");
// this fails: "cannot borrow `*g` as mutable more than once at a time, second mutable borrow occurs here"
// ...
// use the sum and product vars when constructing rules
}
How to approach this?
You can't do this. This is simply the classical "vector push" example in disguise. Let's take a look at this simplified, but semantically equivalent code:
let mut v = Vec::new();
v.push(27);
let twenty_seven = v.last().unwrap();
v.push(3);
println!("{}", twenty_seven);
This does not compile and will never compile in Rust as this is inherently memory unsafe. You have a reference (a pointer) to an element in the vector. But the call to Vec::push might reallocate, invalidating all references to its elements. In C++ it would compile, but lead to UB because you would attempt to read uninitialized or unallocated memory.
The "answer" to your problem is... not simple. You have to think of another structure for your program. Sometimes, using reference counted smart pointers (like Rc) is useful. And it would easily solve your problem. But in many other situations you are better of completely rethinking your application.
Exactly one &mut borrow of data may occur at any given time.
I'd suggest inverting things so that Grammar has a function to add NonTerminals
pub struct NonTerminal {
name: String
}
impl NonTerminal {
pub fn new(name: &str) -> NonTerminal {
Self {
name: name.to_string()
}
}
}
pub struct Grammar {
pub non_terminals: Vec<NonTerminal>
}
impl Grammar {
fn add_non_terminal(&mut self, s: &str) -> &NonTerminal {
self.non_terminals.push(NonTerminal::new(s));
self.non_terminals.last().unwrap()
}
}
pub fn main() {
let mut g = Grammar {
non_terminals: vec![]
};
let product_ref = g.add_non_terminal("Product");
let sum_ref = g.add_non_terminal("Sum");
}
Updated based on feedback from #Lukas Kalbertodt.
Can iterate over all non-terminals via g.non_terminals
Basically a object (struct) is constructed by composing different components. Each concrete component being easily swapped by another component matching the interface (I guess trait).
I'm currently trying to implement with traits which got me into some errors and made me start thinking if this is a common thing in Rust.
// usage example
fn main() {
let obj = MainObject::new(Component1::new(), Component2::new(), Component3());
// Where each component is a type(struct) with some well predefined methods.
}
The main idea behind this is to implement the Component pattern commonly used in games. Basically the game would contain a lot of different objects, with slight variations in behavior and contained data. Instead of having a big class hierarchy, the objects are composed of standard components, more complete example would be.
pub struct Container
{
input: InputHandlerComponent, // Probably a trait
physics: PhysicsComponent, // Probably a trait
renderer: RendererCompoent // Probably a trait
}
impl Container {
fn new(p: PhysicsComponent, i: InputComponent, r: RenderComponent) -> Container {
Container {input: i, physics: p, renderer: r}
}
}
struct ConcretePhysicsComponent;
impl PhysicsComponent for ConcretePhysicsComponent
{
// ...
}
struct ConcreteInputComponent;
impl InputComponent for ConcreteInputComponent
{
// ...
}
struct ConcreteRendererComponent;
impl RendererComponent for ConcreteRendererComponent
{
// ...
}
struct AnotherConcreteRendererComponent;
impl RendererComponent for AnotherConcreteRendererComponent
{
// ...
}
// usage example
fn main() {
let obj = Container::new(ConcreteInputComponent::new(), ConcretePhysicsComponent::new(), ConcreteRendererComponent::new());
// Where each component is a type(struct) with some well predefined methods.
// This is a slightly modified version of this object, with changed rendering behaviour
let obj2 = Container::new(ConcreteInputComponent::new(), ConcretePhysicsComponent::new(), AnotherConcreteRendererComponent::new()); }
It sounds like you are just asking about traits, multiple concrete implementations of that trait, and a wrapper object that restricts itself to types that implement that trait. Optionally, the container can implement the trait by delegating it to the inner object.
trait Health {
fn life(&self) -> u8;
fn hit_for(&mut self, lost_life: u8);
}
#[derive(Debug, Copy, Clone)]
struct WimpyHealth(u8);
impl Health for WimpyHealth {
fn life(&self) -> u8 { self.0 }
fn hit_for(&mut self, lost_life: u8) { self.0 -= lost_life * 2; }
}
#[derive(Debug, Copy, Clone)]
struct BuffHealth(u8);
impl Health for BuffHealth {
fn life(&self) -> u8 { self.0 }
fn hit_for(&mut self, lost_life: u8) { self.0 -= lost_life / 2; }
}
#[derive(Debug, Copy, Clone)]
struct Player<H> {
health: H,
}
impl<H> Health for Player<H>
where H: Health
{
fn life(&self) -> u8 { self.health.life() }
fn hit_for(&mut self, lost_life: u8) { self.health.hit_for(lost_life) }
}
fn main() {
let mut player_one = Player { health: WimpyHealth(128) };
let mut player_two = Player { health: BuffHealth(128) };
player_one.hit_for(12);
player_two.hit_for(12);
println!("{:?}", player_one);
println!("{:?}", player_two);
}
it is not possible to have an array of such Players without using Boxed values
That's correct. An array or vector (or any generic type, really) needs to all be of the same type. This is especially important for arrays/vectors because their memory layout is contiguous and each item needs to be at a fixed interval.
If you were allowed to have different types, then you could have one player that had a health that took 1 byte and another player with health that took 2 bytes. Then all the offsets would be incorrect.
You can implement the Health trait for a Box<Health>, and then the Player objects can be stored sequentially, but they would each have a pointer to the appropriate concrete implementation of Health via the box.
impl<H: ?Sized> Health for Box<H>
where H: Health
{
fn life(&self) -> u8 { (**self).life() }
fn hit_for(&mut self, lost_life: u8) { (**self).hit_for(lost_life) }
}
fn main() {
let mut players = vec![
Player { health: Box::new(WimpyHealth(128)) as Box<Health> },
Player { health: Box::new(BuffHealth(128)) as Box<Health> }
];
for player in players.iter_mut() {
player.hit_for(42);
}
println!("{:?}", players[0].life());
println!("{:?}", players[1].life());
}