#Cant implement trait on enum thats defined outside the crate

42 messages · Page 1 of 1 (latest)

junior cypress
#

you want your users to implement RankValue for Rank themselves?

junior cypress
#

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?

dapper isle
#

hmm, could you give an example?

junior cypress
#

well what is in the RankValue trait?

dapper isle
#
pub trait RankValue {
    fn value(&self) -> i32;
}
#

you mean something like this?

impl Rank {
    pub fn value<T: RankValue>(&self) -> i32 {
        todo!()
    }
}
junior cypress
#

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

dapper isle
#

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

junior cypress
#

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

dapper isle
#

i see, is there any workaround for that?

junior cypress
#

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

dapper isle
#

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

junior cypress
#

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

dapper isle
#

? 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?

junior cypress
#

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

wispy ledge
#

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
dapper isle
wispy ledge
#

the addition of a &self is to make using it nicer (in most cases)

#

and this way its object safe to

dapper isle
#

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>();
}
wispy ledge
dapper isle
#

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

wispy ledge
dapper isle
#

lol why?

wispy ledge
dapper isle
#

ill have a field players: Vec<Player> and each Player will have a cards: Vec<Card> field associated to them representing their hand

wispy ledge
dapper isle
#

i guess so?

wispy ledge
dapper isle
#

oh, that shouldn't involve lifetime hassles though, right?

wispy ledge
#

well you have managed to choose one of the not lifetime involved solutions to this