I currently have a trait named Class. It needs to be dyn-compatible, but it also needs to return an optional subclass. The signature of the function as I have it set up now is fn(&self) -> Option<Box<dyn Subclass<Self>>> so as to not let other classes subclasses apply to one they shouldn't.
The issue is, because since it references Self in it's return type, it's no longer dyn-compatible. Is there a way, either modifying my implementation or providing another one, to go about this? I'd like to mention that Self: !Sized.
Thanks!
- Clover B.-P. Johnson
if you have a x : &dyn Class, then by definition you know nothing about its true type.
thus, what type should x.subclass() return ?
if you cannot answer this question with a single answer, then your type cannot be dyn-compatible.
Option<Box<dyn Subclass>> would be fine, but Option<Box<dyn Subclass<Self>>> would not because there is no such thing as Self (arguably Self is dyn Class, and if you do want that Option<Box<dyn Subclass<dyn Class>>> would also be fine)
It's hard to tell without more context, but this sounds a bit like an X-Y problem. If you're trying to emulate OOP in Rust, there's probably a better way.
In any case, as said above, there's inherently no way to base the return type of a dyn-compatible method on the type of the callee. The whole point of dyn objects is to remove that information.
yuo should probably provide a bit more info about the structure of the two traits it's probably possible to reorganize them in a more ergonomic way
Sorry for the lack of information. Here's a better explanation of what I'm trying to achieve:
I'm implementing TTRPG elements. I would like to have a dynamic method of classes, to allow for multi-classing, along with dynamic subclasses to allow for extending the library with more classes and subclasses. Is it possible to, while retaining object-safety, have a function that returns a subclass trait-object that's restricted by the class? For example, a Fighter class with this subclass signature: fn(&self) -> Option<Box<dyn Subclass<Fighter>>>. An enumerator wouldn't be preferable, because it isn't extendable. Any structure that implements this Class trait would contain a boxed dyn Subclass<Self>.
I'm sorry for the late response, I had schoolwork to do, but I can gladly provide more information.
i find that you are probably making a mistake in what to rapresent as types. i think classes abilities stats etc should be data instead.
a better starting point might be
struct ClassId(u64);
struct AbilityId(u64);
struct Ability{
id:AbilityId,
description:String,
required_level:u32,
.....
}
struct RuleBook{
class_abilities:HashMap<ClassId,Vec<AbilityId>>,
abilities:HashMap<AbilityId,Ability>
......
}
struct Player{
classes:Vec<ClassId>,
abilities:Vec<AbilityId>
.......
}
you will find that a lot of classes and abilities ultimately reuse the same underlying logic
I would probably start with a shape a bit like this (playground) and see where it becomes awkward:
#![allow(dead_code)]
use std::collections::HashMap;
enum Fighter {
Brawler(Character),
Swordsman(Character),
}
enum Mage {
Necromancer(Character),
Wizard(Character),
}
struct Character {
ws: u8,
int: u8,
skills: HashMap<Skill, u8>
}
#[derive(Eq, Hash, PartialEq)]
enum Skill {
SwordFighting,
HandToHand,
}
trait Combat {
fn attack(&self) -> u8;
fn defend(&self) -> u8;
}
impl Combat for Fighter {
fn attack(&self) -> u8 {
match self {
Self::Brawler(c) => c.skills.get(&Skill::HandToHand).unwrap_or(&0) + c.ws,
_ => todo!(),
}
}
fn defend(&self) -> u8 {
todo!()
}
}
fn main() {
let charles = Fighter::Brawler(Character{
ws: 5,
int: 1,
skills: HashMap::from([(Skill::HandToHand, 3),(Skill::SwordFighting,1)])
});
assert_eq!(charles.attack(), 8);
}
Compared to OOP Class-Subclass this looks like it's upside-down but feel's rusty to me. You can always use a mod class to group the class enums so you have common starting point class::Fighter::Brawler if you want.
Anywhere where you have:
- "could be this or that" -> enum,
- "common actions that depend on ..." -> trait,
- "values" -> struct
Reading what you are after, my first reaction was "neat idea". Unfortunately, I think the inclusion of dual or multi classing characters means that mapping the character class to RUST enums is going to end up failing. I suspect you will end up needing the have the character class properties (bonuses, available actions, etc) data driven rather than captured in teh type system. Which is too bad.
For a multi-class system, I'd:
- put the
struct Character at the top
- have a
enum Class
- store a
HashSet<Class> or HashMap<Class, u8> in Character.classes
- probably have fewer traits and a reasonably large
impl Character