22

I'm trying to match on the datatype of a generic field of a struct and react accordingly. My general idea was like this (code doesn't compile):

struct Foo<T> {
    bar: T,
}

fn main() {
    let x = Foo::<String> {
        bar: "world".to_string(),
    };

    match x.bar {
        String => println!("It's a string!"),
        u32 => println!("It's a u32!"),
        _ => println!("Something else"),
    };

    println!("end of program!");
}

The error message from the compiler:

warning: unreachable pattern
  --> src/main.rs:12:9
   |
11 |         String => println!("It's a string!"),
   |         ------ matches any value
12 |         u32 => println!("It's a u32!"),
   |         ^^^ unreachable pattern
   |
   = note: `#[warn(unreachable_patterns)]` on by default

warning: unreachable pattern
  --> src/main.rs:13:9
   |
11 |         String => println!("It's a string!"),
   |         ------ matches any value
12 |         u32 => println!("It's a u32!"),
13 |         _ => println!("Something else"),
   |         ^ unreachable pattern

warning: unused variable: `String`
  --> src/main.rs:11:9
   |
11 |         String => println!("It's a string!"),
   |         ^^^^^^ help: consider prefixing with an underscore: `_String`
   |
   = note: `#[warn(unused_variables)]` on by default

warning: unused variable: `u32`
  --> src/main.rs:12:9
   |
12 |         u32 => println!("It's a u32!"),
   |         ^^^ help: consider prefixing with an underscore: `_u32`

warning: variable `String` should have a snake case name
  --> src/main.rs:11:9
   |
11 |         String => println!("It's a string!"),
   |         ^^^^^^ help: convert the identifier to snake case: `string`
   |
   = note: `#[warn(non_snake_case)]` on by default

What I wanted was for x to match the first one. I'm actually not sure what I want to do can be done, but what would achieve the desired effect?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Dash83
  • 1,357
  • 2
  • 17
  • 31
  • The short answer is "not right off the bat". You might find this question useful: [How does Rust implement reflection?](http://stackoverflow.com/q/36416773/1233251) – E_net4 Jan 11 '17 at 17:41
  • Rust uses `CamelCase` for structs, enums, and traits. You should be using `Foo`. – Shepmaster Jan 11 '17 at 18:39

2 Answers2

39

Idiomatic Solution

Create a trait which constrains the parameter T in Foo, implement any specific behavior as an associated function of this trait.

Example:

trait PrintMe {
    fn print_me(&self);
}

impl PrintMe for String {
    fn print_me(&self) { println!("I am a string"); }
}

struct Foo<T: PrintMe> {
    bar: T
}

fn main() {
    // ...
    x.bar.print_me();
}

This is principled generic programming, where you declare exactly the difference of behavior of the possible generic parameters, so that there is no surprise.

See also:


Exact Solution

Rust can indeed query types: each type has a unique TypeId associated, and you can match on TypeId with a series of if checks. It's clunky.

fn print_me<T>(x: &Foo<T>) {
    if TypeId::of::<T>() == TypeId::of::<String>() {
        println!("I am a string");
    } else // ...
}

But please... don't do that :)

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • 1
    Thank you, Matthieu. That clears things up. I think I need to change my design in general, I keep laying out my code "C style". Thanks! – Dash83 Jan 11 '17 at 21:37
  • 3
    +1 for "don't do that", and I'll just point out that Rust *has* `enum`, which is a better (shorter, clearer, idiomatic and compile-time checked) way of writing all those `TypeId::of` checks. – trent Jan 11 '17 at 22:52
  • 1
    @trentcl: It depends. `enum` is for closed-polymorphism whereas `trait` is for open-polymorphism... if that makes sense. – Matthieu M. Jan 12 '17 at 07:35
  • @MatthieuM. It makes sense. You'd already mentioned traits, though, so I thought I'd point out the other solution – trent Jan 12 '17 at 15:29
3

A more general look at matching over types

Basically, you hardly can match on the type, instead you have to use rust's traits.

Here is a helpful introduction for Rust's traits:
https://doc.rust-lang.org/rust-by-example/trait.html

I will explain simply how to match over type so it's okay if you don't know how to use traits.

Here is how to more generally change behavior depending on the type:

  1. Define a trait that is going to do something depending on the type.

  2. Add a function that takes in what you need to do something with it. It will take the variable with a type that varies as self. Don't add a body to that function, just follow the definition with a semicolon ;.

    trait DoSomething {
        fn someFunction(&self);
    }
    
  3. Implement the trait for all the types you wanted to match for: String and u32. This is the part where you can code different behavior for each type.

    impl DoSomething for String {
        fn someFunction(&self) {
            println!("It is a string!")
        }
    }
    impl DoSomething for u32 {
        fn someFunction(&self) {
            println!("It is a u32!")
        }
    }
    
  4. Instead of matching on an element, call the method in the trait on it.

    struct Foo<T> {
        bar: T,
    }
    
    fn main() {
        let x = Foo::<String> {
            bar: "world".to_string(),
        };
    
        // the call of the method someFunction will differentiate the type of x.bar
        x.bar.someFunction();
    }
    

TLDR: in Rust, to match over type, we create a trait, implement a function for each type and call it on the element to match.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578