Runtime instance type disambiguation via TypeId?


#1

So, recently I’ve found myself using enums to distinguish type a lot, and it’s quite tedious work.

Here’s a specific example:

struct NodeHead;
struct NodeTail;

trait Node { fn foo(); }
impl Node for NodeHead { fn foo() {} }
impl Node for NodeTail { fn foo() {} }

enum SomeNode {
  IsHead(NodeHead),
  IsTail(NodeTail)
}

fn handle(node:SomeNode) {
  /* common node handling code here */
  match node {
    IsHead(_) => { /* specific code for handling head nodes*/ },
    IsTail(_) => { /* specific code for handling tail nodes */ }
  }
}

fn main() {
  handle(IsHead(NodeHead));
  handle(IsTail(NodeTail));
}

I’ve found myself using this pattern instead, because it’s less tedious; note specific there’s no need to wrap every value in an enum, although it does require adding the id() call to the trait:

use std::intrinsics::TypeId;

struct NodeHead;
struct NodeTail;

trait Node { 
  fn foo(&self);
  fn id(&self) -> TypeId; 
}
impl Node for NodeHead { 
  fn foo(&self) {} 
  fn id(&self) -> TypeId { return TypeId::of::<NodeHead>(); } 
}
impl Node for NodeTail { 
  fn foo(&self) {} 
  fn id(&self) -> TypeId { return TypeId::of::<NodeTail>(); } 
}

fn is<T: 'static>(item:&Node) -> bool {
  item.foo();
  return TypeId::of::<T>() == item.id();
}

fn handle(node:&Node) {
  /* Common node handling here */
  if is::<NodeHead>(node) {
    /* Head specific handling here */
  }
  else if is::<NodeTail>(node) {
    /* Tail specific handling here */
  }
}

fn main() {
  handle(&NodeHead);
  handle(&NodeTail);
  handle(&NodeTail);
}

It strikes me that it would pretty useful to modify TypeId have something like:

fn as<U, V>(instance:V) -> Option<&U>;
fn of_trait<T>() -> TypeId;

Particularly if you could conveniently convert a trait directly back into a reference to the actual type, something like:

fn handle(node:&Node) {
  match TypeId::as::<&Node, NodeHead>() {
    Some(n) => { .. }, None = {}
  }
  match TypeId::as::<&Node, NodeTail>() {
    Some(n) => { .. }, None = {}
  }
}

or:

fn handle(node:&Node) {
  match TypeId::of_trait(node) {
    TypeId::of::<NodeHead>() => { ... } 
    TypeId::of::<NodeTail>() => { ... },
  }
}

There are certainly arguments against it; if you have access to the trait, you shouldn’t be able to ‘look inside it’; that breaks the back box functionality of treating all trait implementations equally.

However, practically speaking I find that rust code seems to grow pointless enums purely for the purpose of disambiguation of traits and related types; the code turns into a mess of IsA, IsB, IsC, isNone, and it’s practically frustrating to use, because, for example, IsA(foo) takes the value of foo, where a return of &foo would work, a return of Option<&Foo> won’t.

Anyone else think this sort of thing would be useful?

I’m happy enough to just use helper functions for this, but it seems like something that would be practically useful for handling traits in general.


#2

This seems very similar to the std::any::Any type.


#3

@huon oh, I didn’t realize AnyRefExt existed.

Still, I couldn’t make it actually work. I suppose something like this should work?

extern crate core;

use core::any::{Any, AnyRefExt};

trait Foo {
  fn foo();
}

struct FooA;
impl Foo for FooA { fn foo() {} }

struct FooB;
impl Foo for FooB { fn foo() {} }

fn match_foo(f:&Foo) {
  let a = f as &Any;
  let ar = a as &AnyRefExt;
  if ar.is::<FooA>() {
    println!("Is FooA");
  }
  if ar.is::<FooB>() {
    println!("Is FooA");
  }
}

fn dunno_which_foo(f:&Foo) {
  match_foo(f);
}

fn main() {
  {
    let fooa = FooA;
    let foo:&Foo = &fooa;
    dunno_which_foo(foo);
  }
  {
    let foob = FooB;
    let foo:&Foo = &foob;
    dunno_which_foo(foo);
  }
}

But I get:

any.rs:18:9: 18:21 error: type `&core::any::AnyRefExt<'_>` does not implement any method in scope named `is`
any.rs:18   if ar.is::<FooA>() {
                  ^~~~~~~~~~~~
any.rs:21:9: 21:21 error: type `&core::any::AnyRefExt<'_>` does not implement any method in scope named `is`
any.rs:21   if ar.is::<FooB>() {
                  ^~~~~~~~~~~~
any.rs:17:12: 17:27 error: failed to find an implementation of trait core::any::AnyRefExt<'_> for core::any::Any
any.rs:17   let ar = a as &AnyRefExt;
                     ^~~~~~~~~~~~~~~

#4

You are not supposed to use AnyRefExt as a trait object, to call is you have to have a &Any and call it on that. To combine it with your Foo trait however, you’d have to make Foo inherit from Any and manually implement AnyRefExt for &Foo because Rust does not support trait upcasting atm.