#Cant implement trait on enum thats defined outside the crate
42 messages · Page 1 of 1 (latest)
yes
yeah you can't do that
you'll need some other way to allow users to give these mappings
maybe Rank is generic over a T: RankValue, and then users can provide their own type that implements RankValue, and you could use the mapping from T
does that make sense?
hmm, could you give an example?
well what is in the RankValue trait?
pub trait RankValue {
fn value(&self) -> i32;
}
you mean something like this?
impl Rank {
pub fn value<T: RankValue>(&self) -> i32 {
todo!()
}
}
Yeah, maybe if RankValue::value takes a Rank instead of &self too
yeah it wouldn't be as ergonomic, but sadly coherence rules say we can't implement a trait we don't own for a type we don't own
ok so i've got this```rs
impl Rank {
pub fn value<T: RankValue>(&self) -> i32 {
T::value(self)
}
}
pub trait RankValue {
fn value(rank: &Rank) -> i32;
}
no, user would have to do something like
struct MyRankValue;
impl RankValue for MyRankValue {
fn value(rank: &Rank) -> i32 { ... }
}
fn foo(rank: Rank) {
let value = rank.value::<MyRankValue>();
}
the issue is that if an user could implement it, he could then also import a crate that also had it's own definition, and then you would have 2 implementations of the same trait for the same type which isn't allowed
i see, is there any workaround for that?
not really, your users will need to explicit the method they're using to value Rank
it's similar to something like filter on iterators. there are many ways to filter an iterator, so when filtering the user picks one
if there's many ways to value Rank, the user will need to pick one
it doesn't even need to be a trait, you just need a Fn(&Rank) -> i32
also, i've seen you're using &Rank
is there any reason why Rank can't be Copy? it would make working with it much easier
wouldn't that make the value function take ownership of the rank?
i use reference because i dont want it to get moved, if that makes sense
yes, but that's okay if Rank is Copy
if Rank is just an enumeration of the possible card faces, it is a very simple type.
it doesn't need to implement Drop, it doesn't manage any resources (like how Vec and String manage memory)
this means you can just copy it around, and don't need to worry with ownership
? sounds contrary to what i've been learning about ownership, does it mean rank will just live after it goes out of scope?
it doesn't drop?
it drops, but it can just be deleted from the stack
this is different from a type like Vec. Vec holds an internal pointer to some heap allocation. when you first push an element to a Vec, it will allocate some memory to store it's data. when the Vec goes out of scope, we need to deallocate that internal pointer, to free the memory the Vec needed. not freeing memory is a memory leak
that's why Vec moves when you pass it to a function, because it's memory is freed when it leaves the scope of that function. if two things had the same Vec, one could free the memory and the other wouldn't know about it
but if your type doesn't need to do anything when it drops, you can copy it
types like i32, bool, and char are copy types, when you pass them to functions they aren't moved
So this is what I would do from your initial question (I haven't managed to read the entire message history yet)
// In your crate
enum Rank {
Ace, Five, Jack
}
trait RankValue {
fn value(&self, rank: Rank) -> i32;
}
// In user crate
struct MyValues;
impl RankValue for MyValues {
fn value(&self, rank: Rank) -> i32 {
match rank {
...
}
}
}
// then pass a MyValues to whatever needs to use RankValue
hmm i think i get it, but i'll definitely need to spend some more time with rust to fully understand this, so much to think about
that works too
the addition of a &self is to make using it nicer (in most cases)
and this way its object safe to
which is better in design?
// lib.rs
#[derive(Debug, Clone, EnumIter)]
pub enum Rank {
Ace,
Two,
...
}
pub trait RankValue {
fn value(&self, rank: &Rank) -> i32;
}
// main.rs
struct Blackjack;
impl RankValue for Blackjack {
fn value(&self, rank: &Rank) -> i32 {
match rank {
Rank::Ace => 1,
Rank::Two => 2,
...
}
}
}
fn main() {
...
let value = Blackjack.value(&card.rank);
}
or
// lib.rs
#[derive(Debug, Clone, EnumIter)]
pub enum Rank {
Ace,
Two,
...
}
impl Rank {
pub fn value<T: RankValue>(&self) -> i32 {
T::value(self)
}
}
pub trait RankValue {
fn value(rank: &Rank) -> i32;
}
// main.rs
struct Blackjack;
impl RankValue for Blackjack {
fn value(rank: &Rank) -> i32 {
match rank {
Rank::Ace => 1,
Rank::Two => 2,
...
}
}
}
fn main() {
let value = card.rank.value::<Blackjack>();
}
depends how the rest of your code uses this
one thing i like about your approach is that i can access self
because the ace value will depend on the player hand
which i can access from the Blackjack struct
I have a feeling you're going to run into lifetime issues in the near future 
lol why?
how is Blackjack going to have access to the player's hand?
ill have a field players: Vec<Player> and each Player will have a cards: Vec<Card> field associated to them representing their hand
so Blackjack will own all the players?
i guess so?
you will need to add a parameter to value to know which player your asking about
oh, that shouldn't involve lifetime hassles though, right?
well you have managed to choose one of the not lifetime involved solutions to this