Reputation: 35216
My question title is a little vague, but in essence I want to achieve the following:
&Content
get a reference to a behaviour, if it is implemented.This is broadly speaking possible by forcing Content
to know explicitly about BehaviourA
, etc. like:
fn as_a(&self) -> Option<&BehaviourA>
However, this requires that the Content
type explicitly knows about all behaviour types, which is not possible if Content
is exported as an api in a crate.
Ideally Content
would have an api like:
fn behaviour<T>(&self) -> Option<T> where T: Any;
Which could be invoked at runtime as:
match content_item.behaviour::<&BehaviourA>() {
Some(x) => { ... },
None => {}
}
However, I cannot find any way to implement this function.
My best attempt reads like this:
fn behaviour<T>(&self) -> Option<T> where T: Any {
let own_type = TypeId::of::<T>();
if own_type == TypeId::of::<Behaviour>() { return Some(self as T); }
return None;
}
But this results in:
src/foo.rs:21:62: 21:71 error: non-scalar cast: `&interface::test::Foo` as `T`
src/foo.rs:21 if own_type == TypeId::of::<Behaviour>() { return Some(self as T); }
I appreciate there is no way to introspect a list of traits on an object at runtime, but here I'm explicitly casting to a known type in the implementation; it's simply that the bound T is not always satisfied.
Is there any way of doing this at all, even unsafely?
An example playpen showing the error is here: http://is.gd/fZNFA8
(NB. I have seen Can you restrict a generic to T where T is a trait implemented by Self?, but my question is different; in a nutshell, is there any way, even unsafely to generate a trait reference at runtime?)
Upvotes: 1
Views: 881
Reputation: 59145
The fundamental problem here is that traits aren't types. When you say &BehaviourA
, you're not referring to the trait, you're referring to the object-safe type which is derived from the trait. What you need to do is tell Rust that T
isn't a type, but a trait.
Tragically, as of 1.6, there's still no way of doing this.
The only way I know of to make something like this work is explicitly, a-la COM: you need a base interface that allows you to ask "cast to a reference to X
", then implement that on each concrete type for all supported X
.
If you're willing to dip into unsafe, messy code, you can get something vaguely like what you want with some macros (playpen):
// Requires nightly compiler, since it depends on implementation details.
#![feature(raw)]
use std::any::TypeId;
use std::mem::transmute;
use std::raw::TraitObject;
macro_rules! qi {
($e:expr, $t:ty) => {
unsafe {
let iid = TypeId::of::<$t>();
let ptr = &$e;
match ptr.query_interface(&iid) {
Some(to) => {
let tmp: &$t = transmute(to);
Some(tmp)
},
None => None,
}
}
};
}
macro_rules! impl_unknown_for {
($base:ty: $($ints:ty),*) => {
unsafe impl Unknown for $base {
fn query_interface(&self, iid: &TypeId) -> Option<TraitObject> {
unsafe {
$(
if *iid == TypeId::of::<$ints>() {
let to = transmute::<&$ints, _>(self);
return Some(to);
}
)*
None
}
}
}
};
}
unsafe trait Unknown {
fn query_interface(&self, iid: &TypeId) -> Option<TraitObject>;
}
trait BehaviourA { fn a(&self); }
trait BehaviourB { fn b(&self); }
trait BehaviourC { fn c(&self); }
struct Thingy;
impl_unknown_for!(Thingy: BehaviourA, BehaviourC);
impl BehaviourA for Thingy {
fn a(&self) { println!("Thingy::a"); }
}
impl BehaviourC for Thingy {
fn c(&self) { println!("Thingy::c"); }
}
fn main() {
let obj: Box<Unknown> = Box::new(Thingy);
if let Some(ba) = qi!(obj, BehaviourA) { ba.a(); }
if let Some(bb) = qi!(obj, BehaviourB) { bb.b(); }
if let Some(bc) = qi!(obj, BehaviourC) { bc.c(); }
}
Upvotes: 1