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.