Rust能力养成系列之(21) :泛型实现和使用

前言

 

如题,我们看下泛型的实现和对应的用法。

泛型实现(Generic implementations)

我们也可以为泛型类型编写impl块,但由于额外的泛型类型参数,形式上会变得冗长。让我们在 Container<T> 结构体上实现一个new()方法,键入rustc 加文件名,编译一下。

// generic_struct_impl.rs
 
struct Container<T> {
    item: T
}
 
impl Container<T> {
    fn new(item: T) -> Self {
        Container { item }
    }
}
 
fn main() {
    // stuff
}

报错如下,

https://note.youdao.com/yws/public/resource/8ca3d85a06f2ee040d07b93c7397d7db/xmlnote/6B03BFCFC24B486B85185768529138EA/57476

上述报错是说,找不到泛型类型T。当我们为任何泛型类型写impl块时,需要在使用之前声明泛型类型的参数。换句话说,T就像一个变量——一个类型变量——我们需要声明它。因此,这里需要修改impl块,在impl后添加<T>,如下所示:

impl<T> Container<T> {
    fn new(item: T) -> Self {
        Container { item }
                            }
                      }

于是整体代码如下

// generic_struct_impl.rs
 
struct Container<T> {
    item: T
}
//impl<T> Container<T>
impl<T> Container<T> {
    fn new(item: T) -> Self {
        Container { item }
    }
}
 
fn main() {
    // stuff
}
 

通过更改,可以编译前面的代码了,伴随两个合理的warning。

https://note.youdao.com/yws/public/resource/8ca3d85a06f2ee040d07b93c7397d7db/xmlnote/7A1097EE9692466E82A048F5B49A45C4/57501

前面的impl块基本上意味着我们正在为所有类型T实现这些方法,其都出现在容器<T>中。这个impl块是一个泛型实现。因此,每个生成的具体容器都将具有这些方法。现在,也可以为容器<T>写一个更具体的impl块,通过把任何具体的类型来代替T,比如以下所示:

impl Container<u32> {
    fn sum(item: u32) -> Self {
        Container { item }
    }
}

在上面的代码中,我们实现了一个名为sum的方法,它只在容器<u32>类型上存在。这里,我们不需要impl后面的<T>,因为u32是一个具体的类型。这是impl块的另一个很好的属性,允许开发者通过独立实现方法来专门化泛型类型。

 

使用泛型

现在可以看到,实例化或使用泛型类型的方式与非泛型的对应类型有所不同。当我们进行实例化时,编译器需要知道的类型T的具体类型,也就是signature,用来提供类型信息以使泛型代码单一性化。大多数情况下,具体类型是基于类型的实例化推断,或者在泛型函数的场景下,通过调用接受具体类型的任何方法推断的。在极少数情况下,可能需要通过使用turbofish (::<>) 操作符来专门输入具体类型代替泛型类型,以帮助编译器确定。稍后会介绍这是如何使用的。

 

让我们看看实例化Vec<T>的例子,这是一个泛型类型。如果没有任何类型签名,以下代码将无法编译:

// creating_generic_vec.rs
 
fn main() {
    let a = Vec::new();
}

报错如下:

https://note.youdao.com/yws/public/resource/8ca3d85a06f2ee040d07b93c7397d7db/xmlnote/D9782BAD462F4FCFA57A78BD68326C32/57535

这是因为此时编译器不知道a将包含什么类型,直到我们手动去指定或调用相关的方法之一,从而可以传入一个具体的值。如下代码:

// using_generic_vec.rs
 
fn main() {
    // providing a type
    let v1: Vec<u8> = Vec::new();
 
    // or calling method
    let mut v2 = Vec::new();
    v2.push(2);    // v2 is now Vec<i32>
 
    // or using turbofish
    let v3 = Vec::<u8>::new();    // not so readable
}

编译通过

https://note.youdao.com/yws/public/resource/8ca3d85a06f2ee040d07b93c7397d7db/xmlnote/160F3403205945008504486D7430CF6B/57544

在第上面的代码片段中,我们将v1的类型指定为u8的Vec,可以很好的编译。与v2一样,另一种方法是调用接受任何具体类型的方法。在push方法调用之后,编译器可以推断出v2是Vec<i32>。创建Vec的另一种方法是使用turbofish操作符,就像前面代码中的v3绑定一样。

 

一般函数中的turbofish操作符出现在函数名之后和括号之前。下一个代码例子是来自std::str模块的通用解析函数。parse可以从字符串中解析值,许多类型都可以从字符串中解析,比如i32、f64、usize等等,所以它是一个泛型类型。因此,在使用parse时,确实需要使用turbofish操作符,如下所示:

// using_generic_func.rs
 
use std::str;
 
fn main() {
    let num_from_str = str::parse::<u8>("34").unwrap();
    println!("Parsed number {}", num_from_str);

 

需要注意的是,只有实现FromStr接口或trait的类型才能传递给parse函数。u8有一个FromStr的实现,因此能够在前面的代码中解析。parse函数使用FromStr trait来限制可以传递给它的类型。在探索完特性之后,我们将了解如何把泛型和特性这两者混搭一起。

 

结语

 

有了泛型的概念,我们就可以马上探索Rust中最普遍的特色了——特性(Trait),想必读者有所感受了吧,我们的教程内容越来越有意味了。

 

主要参考和建议读者进一步阅读的文献

https://doc.rust-lang.org/book

Rust编程之道,2019, 张汉东

The Complete Rust Programming Reference Guide,2019, Rahul Sharma,Vesa Kaihlavirta,Claus Matzinger

Hands-On Data Structures and Algorithms with Rust,2018,Claus Matzinger

Beginning Rust ,2018,Carlo Milanesi

Rust Cookbook,2017,Vigneshwer Dhinakaran

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值