Rust是否会擦除泛型类型?

20
在Rust中是否存在泛型类型擦除(类似于Java)?我找不到明确的答案。

2
为了获得最有用的回复,您可能需要定义“类型擦除”的含义。 - Shepmaster
4
在像Java和Scala这样的JVM语言中,泛型的类型擦除是一个明确定义的概念,但在我所知道的其他任何语言中,泛型都不会发生类型擦除。精通现代Java的人将知道“类型擦除”的含义。 - Iceberg
这篇文章很好地解释了Rust和Java泛型以及类型系统特性之间的轻微差别。链接 - ceving
2个回答

28
当你使用泛型函数或泛型类型时,编译器会为每个不同的类型参数集生成一个单独的实例(我认为生命周期参数被忽略了,因为它们对生成的代码没有影响)。这个过程被称为单态化。例如,Vec<i32>Vec<String>是不同的类型,因此Vec<i32>::len()Vec<String>::len()是不同的函数。这是必要的,因为Vec<i32>Vec<String>具有不同的内存布局,因此需要不同的机器码!因此,,没有类型擦除。
如果我们像以下示例中使用Any::type_id()
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());
}

我们得到了两个Vec实例的不同类型ID。这证明了Vec<i32>Vec<String>是不同的类型。
然而,在Rust中,反射能力有限;目前只有Any。您无法获取运行时值的更多类型信息,例如其名称或成员。为了能够使用Any,必须将其强制转换(使用Any::downcast_ref()Any::downcast_mut())为在编译时已知的类型。

看起来你和我有不同的看法 ^_^。也许我没有理解问题的细微差别... - Shepmaster
3
在Java中,类型擦除很重要,因为好像类型参数从来没有出现过一样。例如,你可以将一个 String 存储在一个 ArrayList<Integer> 中,因为在运行时根本就不存在 ArrayList<Integer> 这样的东西,它只是一个 ArrayList。在Java中使用泛型的唯一优势是编译器会在安全的情况下为您插入转换操作,而没有泛型则需要自己输入转换操作(并且可能会出错)。 - Francis Gagné
Vec<Box<Any>> 怎么样? - aochagavia
2
@aochagavia:我不认为这种类型擦除与Java中的方式相同。Vec<Box<Any>>可以让你存储任何实现了Any的类型的对象,但你不能像使用Vec<i32>Vec<String>那样使用它。在Java中,类型擦除只有在类型参数可以被替换为引用类型(不能使用像int这样的原始类型)时才可能发生。由于所有引用类型都具有相同的大小(指针),所有泛型类或方法的实例化将是相同的(除了一些奇怪的情况,如new T[]不起作用)。 - Francis Gagné

4
Rust 的确拥有类型擦除的形式,即通过 dyn Trait 进行虚方法分派,这使得您可以拥有一个具有不同具体类型元素的 Vec
fn main() {
    let list: Vec<Box<dyn ToString>> = vec![Box::new(1), Box::new("hello")];

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

(游乐场)

请注意,编译器要求您手动包装元素,因为它必须在编译时知道每个值的大小。您可以使用一个Box,它指向堆,无论它指向什么,它的大小都相同。您也可以使用&引用:

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

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

(游乐场)

然而,需要注意的是,如果您使用&引用,可能会遇到生命周期问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接