Associate values at compile time - rust

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) }
}
}

Related

Proper way to implement simple collection in rust [duplicate]

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)
Closed 6 months ago.
I'm trying to create a simple collection:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=361258962c9a25b953aab2a9e4999cc9
use std::collections::HashMap;
pub struct User {
pub id: u32,
pub name: String,
}
pub struct UsersIndex<'a> {
list: Vec<User>,
index: HashMap<u32, &'a User>,
}
impl UsersIndex<'_> {
pub fn new() -> Self {
UsersIndex {
list: Vec::new(),
index: HashMap::new(),
}
}
pub fn add(&mut self, user: User) {
self.list.push(user);
self.index.insert(user.id, &user);
}
pub fn get(&self, id: u32) -> Option<&&User> {
self.index.get(&id)
}
}
but can not fix the errors:
use of moved value: user
user does not live long enough
As I understand I have to take ownership of User, but google doesn't help me how to do it.
Rust says that I need to implement the Copy trait, but User contains a field of type String.
The issue with this code is the following:
pub fn add(&mut self, user: User) {
self.list.push(user); // Here, you move user to the list. The list owns user
self.index.insert(user.id, &user); // Then, you create a reference of a moved value
}
So, in your UserIndex struct, you want to store values and references of these values. These are called self-referential structs. With the ownership rules of Rust, you need to use unsafe Rust code to achieve this. If I were you, I'd think of a different way of implementing your collection following the Rust conventions. For example, you could do something like this:
use std::collections::HashMap;
pub struct User {
pub id: u32,
pub name: String,
}
pub struct UsersIndex {
index: HashMap<u32, User>, // I only use the HashMap. The HashMap owns the User structs
}
impl UsersIndex {
pub fn new() -> Self {
UsersIndex {
index: HashMap::new(),
}
}
pub fn add(&mut self, user: User) {
self.index.insert(user.id, user); // user is moved to the HashMap
}
pub fn get(&self, id: u32) -> Option<&User> { // Instead of a nested reference, we get a regular reference
self.index.get(&id)
}
}
fn main() {
let user = User {
id: 42,
name: "test".to_string(),
};
let mut index = UsersIndex::new();
index.add(user);
match index.get(42) {
Some(usr) => println!("{}", usr.name),
_ => println!("Not Found"),
}
}
Here you can find a playground with this implementation.
EDIT
If you need different HashMaps for the same User structs depending on the key used, you can use Rc smart pointers. They allow you to create more than one pointer that owns the same struct. It would look more or less like this:
use std::rc::Rc;
use std::collections::HashMap;
pub struct User {
pub id: u32,
pub name: String,
}
pub struct UsersIndex {
index: HashMap<u32, Rc<User>>, // Keys will be the user indeces
name: HashMap<String, Rc<User>>, // Keys will be the user names
}
impl UsersIndex {
pub fn new() -> Self {
UsersIndex {
index: HashMap::new(),
name: HashMap::new()
}
}
pub fn add(&mut self, user: User) {
// user will be moved, so we copy the keys before that:
let user_id = user.id;
let user_name = user.name.clone();
let user_rc = Rc::new(user); // We create the Rc pointer; user is moved to user_rc
self.index.insert(user_id, user_rc.clone()); // Rc pointers can be cloned
self.name.insert(user_name, user_rc); // user_rc is moved to the self.name HashMap
}
pub fn get(&self, id: u32) -> Option<Rc<User>> {
match self.index.get(&id) {
Some(user) => Some(user.clone()),
None => None
}
}
}
fn main() {
let user = User {
id: 42,
name: "test".to_string(),
};
let mut index = UsersIndex::new();
index.add(user);
match index.get(42) {
Some(usr) => println!("{}", usr.name),
_ => println!("Not Found"),
}
}
Here you can find a playground with this new implementation.
Again, if you also need the User structs to be mutable, then you'll probably need to use an Rc<RefCell<User>> (link here).
Hope you find this useful!

How do I 'force' structs to implement the same traits?

I have the following:
pub struct OpBStruct {
title: String,
output_vale: i32,
}
impl OpBStruct {
pub fn new_OpB(in_title: String, in_output_vale: i32) -> OpBStruct {
OpBStruct {
title: in_title,
output_vale: in_output_vale,
}
}
}
pub struct OpCStruct {
title: String,
another_value: String,
output_vale: i32,
}
impl OpCStruct {
pub fn new_OpC(in_title: String, in_another_value: String, in_output_vale: i32) -> OpCStruct {
OpCStruct {
title: in_title,
another_value: in_another_value,
output_vale: in_output_vale,
}
}
}
impl A {
pub fn new_A(in_name: String, in_operator: Op) -> A {
A {
name: in_name,
operator: in_operator,
}
}
}
pub enum Op {
OpB(OpBStruct),
OpC(OpCStruct),
}
pub struct A {
name: String,
operator: Op,
}
impl A {
pub fn new_A(in_name: String, in_operator: Op) -> A {
A {
name: in_name,
operator: in_operator,
}
}
}
The exact structure of OpBStruct and OpCStruct are arbitrary and could be anything.
How do I make sure OpBStruct and OpCStruct implement a certain trait?
trait OpTrait {
pub fn get_op_output(&self) -> i32;
}
I thought about making a sort of constructor function that checked for an OpTrait trait requirement and it would be the only way one could create an Op instance, but each operator requires different initialization parameters and there's no way to specify a variable number of inputs for a function in Rust.
Something like this doesn't work because there's no way to input the initialization parameters:
pub fn new_op<T: OpTrait>(operator: T) {
// --snip--
}
I thought about somehow using the new_A method implemented on A to check if the in_operator has implemented the trait, but I'm not sure how to do that either.
What is the correct pattern for this? If there is none, I can just implement the trait for each Op with no sort of interface around it.
I would also recommend writing a test, however you can write a function which is generic over a type but takes no arguments:
struct X {}
trait Y {
fn yo();
}
fn is_y<T: Y>(){}
Then you can add the following line to do the check
is_y::<X>();
which will compile only if X implements Y.
Using a unit test would be a fairly straightforward way to enforce that you want a given trait on a struct. You can do it via implicit test code, but a small utility function to do so can express the intent a little more clearly.
If you have indicated trait inputs on functions in the rest of the code, it might come out fairly naturally without the unit test. The test has the advantage of letting you have an opportunity to explicitly check some invariant of the trait implementation too.
struct A {
val: u8,
}
struct B {
val: u32,
}
trait ExpandToU64 {
fn to_u64(&self) -> u64;
}
impl ExpandToU64 for A {
fn to_u64(&self) -> u64
{
self.val as u64
}
}
fn trait_tester<E>(a: E)
where E: ExpandToU64
{
// the utility function doesn't have to even use the trait...
// but you probably want to exercise the logic a bit
//let v = a.to_u64();
let v = 24u64;
println!("{:?}", v);
}
#[test]
fn test_needs_trait_ExpandToU64() {
let a = A { val:1 };
trait_tester(a);
let b = B { val:2 };
trait_tester(b);
// This fails with a compile error
// "the trait `ExpandToU64` is not implemented for `B`"
}

How to implement a find_or_create method for a struct containing a hashmap with mutable references

I'm having problems with a struct containing a hashmap in rust.
Lets say I have the following types:
pub type KeyType i32;
pub enum StatusType { Locked, Unlocked }
pub struct Entry {
key: KeyType,
status: StatusType
}
pub struct Manager<'a> {
map: HashMap<KeyType, &'a mut Entry>
}
I want to define a method on Manager that takes a key and returns either a unlocked entry if not found, or the existing entry if it exists. Here is the pseudo code:
impl<'a> Manager<'a> {
pub fn find_or_create_entry(&'a mut self, key: KeyType) -> &'a mut Entry {
match self.map.get(&key) {
Some(e) => e,
None => {
// create new entry, add to map, and return mutable ref
}
}
}
}
I haven't been able to figure out how this works in Rust. Any pointers?
I got around this by changing the type of the HashMap to HashMap<KeyType,Box<Entry>>, and implemented the method as follows:
pub fn get_or_create_entry(& mut self, key: LockKey) -> &mut LockEntry {
let e = Box::new(LockEntry{key: key, status: LockStatus::Unlocked});
self.lock_table.entry(key).or_insert(e)
}
Is there a better way?

Is there an idiomatic way to implement the component pattern?

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());
}

Best way to implement a collection of items for copying?

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.

Resources