Help on implementing subclasses

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