0

I have the following 2 traits: Org, Capability.

#![feature(specialization)]


trait Org {}

struct OrgV1 {}
impl Org for OrgV1 {...}

struct OrgV2 {}
impl Org for OrgV2 {...}

trait Capability {}

struct CapV1 {}
impl Capability for CapV1 {...}

struct CapV2 {}
impl Capability for CapV2 {...}

  // Capability has a generic associated .ensure method that is able to handle 
  // any combination of the concrete types of Org and Capability.
  // It is implemented with rust's specialization feature: https://rust-lang.github.io/rfcs/1210-impl-specialization.html 

trait CapabilityService<O: Org> where Self: Capability {
    fn ensure(self, org_config: Arc<O>) -> Result<bool>
}

impl<O: Org, C: Capability> CapabilityService<O> for C {
    default fn ensure(self, org_config: Arc<O>) -> Result<bool> {
    ... // not so important logic
    return Ok(true)
    }
}    


fn main() {
  ...
  // assume I have the following 2 variables I got in runtime by serde's deserealization:
  // org_trait: instance Org trait object
  // capability_trait: instance Capability trait object
  
  // Given that there is .ensure that can handle any combination of the concrete repo and org
  // is there a way in rust to downcast to a generic concrete type, aka:
  
  let concrete_org = org_trait.as_any().downcast_ref::<T: Org>().unwrap()
  let concrete_capability = capability_trait.as_any().downcast_ref::<T: Capability>().unwrap()
   
  // where concrete_org and concrete_capability are some concrete types of Org, 
  // and Capability respectively.
  
  let result = concrete_capability.ensure(concrete_org)
}

I am aware that I could do something like this:

{

  if let Some(org) = org_trait.as_any().downcast_ref::<OrgV1>() {
     if Some(cap) = cap_trait.as_any().downcast_ref::<CapV1>() {
         cap.ensure(org)
     }
     ...
  } else Some(org) = org_trait.as_any().downcast_ref::<OrgV2>() {
     ...
  }
  ...
}

But it seems like a nightmare to handle once there are more Org/Cap implementations(in my actual case, I have 3 traits that ensure works on, which I didn't include to make the picture a bit clear)

Is it possible to downcast trait to a generic concrete type, given the constraints(since we know that .ensure would be able to be called no matter what the concrete types are)? I would like to avoid writing my own long chain of nested if let Somes if possible

Aibek
  • 318
  • 3
  • 11
  • `we know that .ensure would be able to be called no matter what` Do we? What if I come along tomorrow and `impl Org for MyCheapToaster {}` somewhere? Does `MyCheapToaster` support `.ensure`? Traits *only* support the methods explicitly defined on them, nothing more and nothing less, because Rust always assumes traits are open to future implementations. – Silvio Mayolo Mar 22 '22 at 01:28
  • I think you meant `impl Capability for MyCheapToaster {}`, since `.ensure` is defined in Capability. In any case, to my understanding, that's what specialization would do. It would create `ensure` that would be able to handle the newly created `MyCheapToaster` be it Org, or Capability in this case. It would give it the default `ensure` that is defined in `default fn ensure(...`. – Aibek Mar 22 '22 at 02:09
  • Instead of worrying about downcasting, why not make `CapabilityService::ensure` work with `Org` and `Capability` trait objects? – EvilTak Mar 22 '22 at 02:19
  • This was initially a thought process. We have 3 traits: Repo, Org, Capability. If I were to go the `CapabilityService::ensure` route, we would have to implement complicated logic inside that ensure due to how concrete types of Repo, Org, and Capability interact with each other. Capability can only work with certain repo and org versions. Specialization allows me to create a default ensure that says: "I do not handle such a combination of Org/Repo/Config". And then every combination that works, we implement with a more specialized ensure that handles specific org/repo/capability – Aibek Mar 22 '22 at 02:24
  • "Capability can only work with certain repo and org versions" That's the opposite of "we know that ensure will always work". Does it always work or only work for specific versions? This all smells of an inconsistently-defined API, and *that* is the first problem you need to solve. – Silvio Mayolo Mar 22 '22 at 02:41
  • Org, Repo, and Capability are both supplied externally. Out of all possible combinations of Org, Repo, and Capability, only a small number of them are compatible with each other. It is easier for me to generally say: "this combination is not supported", and then use a more specific definition of `.ensure` to "override" the default definition using a specialization. Ensure returns a Result. If the supplied combination doesn't work, then ensure returns the error in the result. By "always work" it means there is ensure defined for every combination – Aibek Mar 22 '22 at 03:17
  • This sounds like an XY problem. Assuming it's not, the usual way you'd handle this kind of thing is with the visitor pattern using dynamic dispatch, or using enums and `match`. Explicit downcasts are a huge red flag that you're approaching the problem wrong. – cdhowie Mar 22 '22 at 05:41

1 Answers1

0

Thanks all. It seems like I have misunderstood how rust's specialization feature works. Specifically, my thought process was opposite of the note mentioned on the RFCs page: This default impl does not mean that Add is implemented for all Clone data, but just that when you do impl Add and Self: Clone, you can leave off add_assign

Aibek
  • 318
  • 3
  • 11