20

Is there type erasure of generics in Rust (like in Java) or not? I am unable to find a definitive answer.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Anton
  • 2,282
  • 26
  • 43
  • 2
    You may need to define what you mean by "type erasure" in order to get the most useful responses. – Shepmaster Sep 13 '15 at 04:09
  • 4
    @Shepmaster Type erasure for generics is [a well-defined concept](https://docs.oracle.com/javase/tutorial/java/generics/erasure.html) in JVM languages like Java and Scala, but does not occur with generics in any other language I know. People who are proficient in modern Java will know what "type erasure" means. – Iceberg Sep 13 '15 at 23:00
  • This explains it quite well: [A light comparison between Rust and Java generics and type system features.](https://gist.github.com/Kimundi/8391398) – ceving Jun 17 '19 at 07:50

2 Answers2

28

When you use a generic function or a generic type, the compiler generates a separate instance for each distinct set of type parameters (I believe lifetime parameters are ignored, as they have no influence on the generated code). This process is called monomorphization. For instance, Vec<i32> and Vec<String> are different types, and therefore Vec<i32>::len() and Vec<String>::len() are different functions. This is necessary, because Vec<i32> and Vec<String> have different memory layouts, and thus need different machine code! Therefore, no, there is no type erasure.

If we use Any::type_id(), as in the following example:

use std::any::Any;

fn main() {
    let v1: Vec<i32> = Vec::new();
    let v2: Vec<String> = Vec::new();
    
    let a1 = &v1 as &dyn Any;
    let a2 = &v2 as &dyn Any;
    
    println!("{:?}", a1.type_id());
    println!("{:?}", a2.type_id());
}

we obtain different type IDs for two instances of Vec. This supports the fact that Vec<i32> and Vec<String> are distinct types.

However, reflection capabilities in Rust are limited; Any is pretty much all we've got for now. You cannot obtain more information about the type of a runtime value, such as its name or its members. In order to be able to work with Any, you must cast it (using Any::downcast_ref() or Any::downcast_mut() to a type that is known at compile time.

Francis Gagné
  • 60,274
  • 7
  • 180
  • 155
  • Looks like you and I disagree ^_^. Perhaps there is a nuance to the question I am missing... – Shepmaster Sep 13 '15 at 04:08
  • 3
    @Shepmaster: In Java, type erasure is relevant because it's as if the type parameters were never there. For example, you can store a `String` in an `ArrayList`, because there is no such thing as an `ArrayList` at runtime; it's just an `ArrayList`. The only advantage to using generics in Java is that the compiler will insert casts for you where it's safe, whereas without generics, you'd have to type the casts yourself (and you could get them wrong). – Francis Gagné Sep 13 '15 at 04:19
  • What about `Vec>`? – aochagavia Sep 15 '15 at 17:44
  • 2
    @aochagavia: I don't consider this type erasure in the same way as in Java. A `Vec>` would allow you to store objects of any type that implements `Any`, but you could not use it as if it was, say, a `Vec` or a `Vec`. In Java, type erasure is only possible because type parameters can only be replaced with reference types (you can't use primitive types like `int`). Since all reference types have the same size (a pointer), all instantiations of a generic class or method would be identical (bar a few oddities such as `new T[]` which doesn't work). – Francis Gagné Sep 16 '15 at 00:26
4

Rust does have type erasure in the form of virtual method dispatch via dyn Trait, which allows you to have a Vec where the elements have different concrete types:

fn main() {
    let list: Vec<Box<dyn ToString>> = vec![Box::new(1), Box::new("hello")];

    for item in list {
        println!("{}", item.to_string());
    }
}

(playground)

Note that the compiler requires you to manually box the elements since it must know the size of every value at compile time. You can use a Box, which has the same size no matter what it points to since it's just a pointer to the heap. You can also use &-references:

fn main() {
    let list: Vec<&dyn ToString> = vec![&1, &"hello"];

    for item in list {
        println!("{}", item.to_string());
    }
}

(playground)

However, note that if you use &-references you may run into lifetime issues.

Camelid
  • 1,535
  • 8
  • 21