8

I am trying to understand Rust polymorphism. From my background in OOP, I would expect the following Rust code to work:

use std::io::{stdin, Read};

fn main() {
    let r: Read = stdin();
    println!("ok");
}

But it doesn't:

4 |     let r: Read = stdin();
  |                   ^^^^^^^ expected trait std::io::Read, found struct `std::io::Stdin`

I know that there's a Read impl for StdIn, so how can I make this (or whatever is the correct way to do this) work, ie. use Stdin, or a File, or even a String if possible (couldn't find a implementation for that) be used where a Read is expected?

I don't think I can use generics here as I need to pass an instance of r, which can be whatever that implements Read, to another method later, but please tell me if I am wrong.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Renato
  • 12,940
  • 3
  • 54
  • 85

1 Answers1

10

You probably want to use trait objects here. You can use traits in basically two ways:

  • The static dispatch way: fn foo<T: Trait>(x: T). This reads "for an arbitrary, but fixed T which implements Trait".
  • The dynamic dispatch way: fn foo(x: &Trait). This lifts the "but fixed" limitation from the first version by using trait objects.

If you want to have a variable which could either hold a reference to Stdin or a File or anything else that implements Read, you can't go for the static dispatch solution, as your implementing type is not fixed.


So what is the type of your variable then? Read? Sadly, it's not that easy. Trait objects are unsized and can't be used on the stack directly. Instead you can only interact with trait objects via references/pointers, like &Read, &mut Read, Box<Read> and so on. Now that we use borrowing-stuff, we might encounter more problems related to that. Luckily, you haven't been the first one encountering this issue: see this question for a detailed look at this specific use case.

To simplify a bit, in most cases it's ok to use Box<Trait>. In your case, it would look like this:

use std::io::{stdin, Read};

fn main() {
    let r: Box<Read> = Box::new(stdin());
    println!("ok");
}
Community
  • 1
  • 1
Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
  • I was thinking of using a function pointer instead that points to the `Read::read` method, maybe that would be more efficient (and work for the String case as well)? – Renato May 01 '17 at 20:09
  • 1
    @Renato you'd need a pointer to the *implementation* of `Read::read` for the appropriate concrete type and you'd need a pointer to the specific data for that concrete type. That would be two pointers, which Rust glues together and calls a *fat pointer*, and [a trait object **is that**](http://stackoverflow.com/q/27567849/155423). In the linked duplicate, there's an example of using a *trait object reference* (`&Read`) which is what you are asking for. `Box` is a *boxed trait object*. – Shepmaster May 01 '17 at 20:15
  • 2
    @Renato Trait objects use so called *vtables* to achieve runtime polymorphism. These vtables contain function pointers to all the trait's methods! So you don't need to do it manually. Additionally, LLVM knows about the vtable and might perform optimizations. So using trait objects is encouraged. About your `String` problem: use [`Cursor`](https://doc.rust-lang.org/stable/std/io/struct.Cursor.html) which implements `Read`. – Lukas Kalbertodt May 01 '17 at 20:17
  • @Shepmaster I was reading that, very interesting... thanks... my code needs to run very fast, so I think I will use the ugly solution that doesn't require heap allocation. – Renato May 01 '17 at 20:18
  • @LukasKalbertodt very good thanks... I must say I miss Java-style polymorphism in this case, though :) much easier to use. – Renato May 01 '17 at 20:22
  • 1
    @Renato Even though, it's kind of off topic by now, but: remember how to write fast programs. 1. correctness, 2. measure, 3. optimize bottlenecks. Specifically, this implies: don't think about micro-optimizations too early. Ready from a file or stdin will probably be much more expensive than a small heap allocation of the file descriptor. About Java-style polym.: trying to use Java thinking while writing Rust is very annoying, yes. But don't worry, you will get used to the Rust-way of thinking soon enough and then everything seems very nice and logical :) – Lukas Kalbertodt May 01 '17 at 20:25
  • In the realm of guessing about performance, I'd avoid runtime polymorphism completely. Instead, I'd write a function that takes a type implementing `Read` (`fn foo(rdr: R) { ... }` and then call it from each branch, passing in the object. Then the compiler can *monomorphize* the code. – Shepmaster May 01 '17 at 20:43
  • Thanks for the hints... I am only writing Rust to get the best performance I can... micro-optimising is what I am all-about with Rust :D specially in a tiny cli application where this is the main part of the code! But you are right in general, I agree! – Renato May 01 '17 at 20:47
  • 1
    @Renato For what it's worth, "Java-style polymorphism" is not so different: in Java, every object is potentially a reference and every polymorphic method call always goes through a vtable. What Java primarily lacks is a distinction between `&MyStruct` (pointer-to-data) and `&MyTrait` (pointer-to-data-with-vtable). Rust just makes you deal with the extra indirection, more explicitly. – trent May 01 '17 at 22:36