本章覆盖有:
traits如何避免在泛型函数调用中的不能理解的编译错误信息
泛型参数的边界如何被单子化(monolithic),或如何分解为几个traits
如何创建函数体返回内的traits
如何简单地使用self关键字,通过"点,dot notation"操作来创建函数
如何迭代一个trait
如何定义类型别名
如何定义泛型迭代器
如何使用关联类型来简化泛型迭代器的使用
如何定义字节的迭代器
The Need for Traits
假设我们要计算一个4次方根,该函数命名为“quartic_root”。以及调用标准库的sqrt函数,我们会写,
1 2 3 fn quartic_root (x: f64 ) -> f64 { x.sqrt ().sqrt () }let qr = quartic_root (100f64 );print! ("{} {}" , qr * qr * qr * qr, qr);
结果会打印:“100.00000000000003 3.1622776601683795”。
但我们还需要计算32位数的4次方根,于是又,
1 2 3 4 5 fn quartic_root_f64 (x: f64 ) -> f64 { x.sqrt ().sqrt () }fn quartic_root_f32 (x: f32 ) -> f32 { x.sqrt ().sqrt () }print! ("{} {}" , quartic_root_f64 (100f64 ), quartic_root_f32 (100f32 ));
根据前面我们所学知识,我们可以定义泛型函数来处理,于是,
1 2 3 4 5 6 fn quartic_root <Number>(x: Number) -> Number {x.sqrt ().sqrt () } print! ("{} {}" , quartic_root (100f64 ), quartic_root (100f32 ));
但这段代码是不合法的,生成编译错误,“no method named sqrt found for type Number in the current scope”。它意思是说,泛型类型Number没有这个sqrt函数。
在这方面,Rust不同于C++。C++可以通过模板来关联这个泛型函数,
1 2 3 4 5 6 7 8 9 10 #include <iostream> #include <cmath> template <typename Number>Number quartic_root (Number x) { return sqrt (sqrt (x)); } int main () { std::count << quartic_root ((float )100 ) << " " << quartic_root ((double )100 ); }
即使C++中的NUmber泛型类型没有这个可用的sqrt函数,编译器也不知这个表达式是否允许。但当调用quartic_root时,函数被计数,编译期生成两个具体函数quartic_root<float>和quartic_root<double>。这叫“泛型函数实例化 generic function instantiation”,或“function monomorphization”。这种实例化会检测具体的类型。
C++这种方案带来的缺陷很明显,即当出现程序错误时,譬如,
1 2 3 4 5 6 7 8 #include <iostream> #include <cmath> template <typename Number>Number quartic_root (Number x) { return sqrt (sqrt (x)); } int main () { std::count << quartic_root ("Hello" );
编译器会实例化这个const char*类型的具体函数,它会生成sqrt(const char*)的方法签名。但没有这个函数声明,所以会导致出现变异错误。
这个缺陷带来的问题是,这个泛型类型Number,它所提供的具体类型的函数sqrt可能是由某一位开发者编写的,以及另一种具体类型的sqrt又是另外一位开发者编写的。可能两位开发者的sqrt函数签名并不一样!!
另外类似于C++这种quartic_root的实现,代码阅读是晦涩难懂的,因为它大部分变量、函数、类型都属于库实现(实际上有很多库…),而不是接口。要理解它,不仅需要知道它的API使用;还需要知道它的库的实现。
Traits to the Rescue
Rust中为了避免这种类似于C++的编译为题,提供了trait来澄清复杂错误消息的各种情况,因为它更贴近真实软件环境。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 trait HasSquareRoot { fn sq_root (self ) -> Self ; } impl HasSquareRoot for f32 { fn sq_root (self ) -> Self { f32 ::sqrt (self ) } } impl HasSquareRoot for f64 { fn sq_root (self ) -> Self { f64 ::sqrt (self ) } } fn quartic_root <Number>(x: Number) -> Number where Number: HasSquareRoot { x.sq_root ().sq_root () } print! ("{} {}" , quartic_root (100f64 ), quartic_root (100f32 ));
结果将打印:“3.1622776601683795 3.1622777”
第一个trait命名为“HasSquareRoot”,包含函数签名“sq_root”。一个Rust trait是一个函数签名的容器;它表示这个trait有能力使用某些函数。这里表示了HasSquareRoottrait可以在有“HasSquareRoot”的地方调用“sq_root”函数,或者更常规的说法是,任何满足“HasSquareRoot”trait的类型,都可以调用这个sq_root函数。
但究竟哪些类型满足“HasSquareRoot”?没有定义,因此接下来两个语句,使得f32类型和f64类型满足这个trait。换言之,这些impl语句,可以从给定的f32和f64类型调用这个sq_root。
这些impl反映了“HasSquareRoot”仅是一个程序接口,或API,它需要又具体的类型实现。所以当然地,impl语句的函数签名,需要跟原来的前一个方法签名一样。不同的是impl包含有函数实现。
Rust的trait类似于Java或C#接口,或没有方法体的抽象类。
现在有了具体的类型实现了。第四条语句定义了quartic_root泛型函数,参数化类型参数是Number。然而,这个声明有一个新的段:where Number: HasSquareRoot。这种从句叫做——“trait bound”,它是方法签名的一部分。它字面量的意思是,Number泛型类型必须实现HasSquareRoot特质。
代码调用函数是,这个where从句表示“当调用该函数,你必须确保你传递的参数化类型实现了HashSquareRoottrait”。例如这个的100f32和100f64,对应类型是f32和f64。这两种类型都有hasSquareRoot的实现,因此它们是合法参数。但如果替换为“quartic_root(“Hello”));”,这里没有&str的“HasSquareRoot”的实现,因此违反了条约。以及会得到编译错误“the traitbound &str: main::HasSquareRoot is not satisfied”。
又或者你替换为“quartic_root(81i32));”,也会得到编译错误,因为“HasSquareRoot”没有i32类型的实现。
注意的时,x表达式在函数体内,它的类型仅可能是Number,实际类型并不清楚,所以你不能将x.sq_root(),写为x.abs().sq_root()这种,编译错误abs()在Number范围内没有定义。
Generic Functions with No Trait Bounds
不带特质边界的泛型函数是很少见的,比如这段代码,
1 2 3 4 5 let mut a = 'A' ;let mut b = 'B' ;print! ("{}, {}; " , a, b);std::mem::swap (&mut a, &mut b); print! ("{}, {};, a, b);
泛型函数swap的方法签名是:fn swap<T>(x: &mut T, y: &mut T)。它不需要使用where从句进行trait bound。因为它直接交换了两个对象的地址。实际编码过程中,泛型函数,类型参数总是需要边界绑定的。Rust代码设计,总是强调类型安全这个概念,也是我们编写代码的原则。
Scope of Traits
前面用了一个sq_root来区分标准库的sqrt函数,不过我们也可以将其命名为sqrt,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 fn sqrt () {}trait HasSquareRoot { fn sqrt (self ) -> Self ; } impl HasSquareRoot for f32 { fn sqrt (self ) -> Self { f32 ::sqrt (self ) } } impl HasSquareRoot for f64 { fn sqrt (self ) -> Self { f64 ::sqrt (self ) } } fn quartic_root <Number>(x: Number) -> Numberwhere Number: HasSquareRoot { x.sqrt ().sqrt () } sqrt ();print! ("{} {}" , quartic_root (100f64 ), quartic_root (100f32 ));
同一个作用范围内是不允许有同名方法的。不过上面代码是合法的;因为它们并不作用在同一个scope。fn sqrt()是个本地函数,在HasSquareRoot外;fn sqrt(self)作用在HasSquareRoot内;f32::sqrt和f64::sqrt是个标准库调用。
Traits with Numltiple Functions
前面的例子有个问题是,如果传入的是“-100f64”或“-100f32”,程序会打印“NaN,Not a Number”,我们想处理负数的情况,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 trait HasSquareRoot { fn sq_root (self ) -> Self ; } impl HasSquareRoot for f32 { fn sq_root (self ) -> Self { f32 ::sqrt (self ) } } impl HasSquareRoot for f64 { fn sq_root (self ) -> Self { f64 ::sqrt (self ) } } trait HasAbsoluteValue { fn abs (self ) -> Self ; } impl HasAbsoluteValue for f32 { fn abs (self ) -> Self { f32 ::abs (self ) } } impl HasAbsoluteValue for f64 { fn abs (self ) -> Self { f64 ::abs (self ) } } fn quartic_root <Number>(x: Number) -> Number where Number: HasSquareRoot + HasAbsoluteValue { x.abs ().sq_root ().sq_root () }
多种类型,可以组合不同trait,使用+。
Methods
目前我们接触到的函数的调用方式有两种,一种是f(x, y),另一种是x.f(y)。例如之前例子的String::new()、String::form("")写法,和"abcd".to_string(),"abcd".len()。一种是点操作,一种是函数调用操作。
任何函数都可以使用者两种调用方式,
1 2 3 4 5 6 7 8 9 10 11 print! ("{}," , "abcd" .to_string ());print! ("{}," ,[1 ,2 ,3 ].len ());let mut v1 = vec! [0u8 ; 0 ];v1.push (7u8 ); print! ("{:?}; " , v1);print! ("{}," , std::string::ToString ::to_string ("abcd" ));print! ("{:?}," , <[i32 ]>::len (&[1 , 2 , 3 ]));let mut v2 = vec! [0u8 ; 0 ];Vec ::push (&mut v2, 7u8 );print! ("{:?}" , v2);
虽然可以这样做,但有scoping问题。在标准库中,有很多同名的函数to_string、len、push…。使用点操作,自然会选择适当的函数。但是使用函数调用,函数的范围必须显式写明。例如,to_string的范围在std::string::ToString,len函数的范围在<[i32]>,push的作用范围在Vec。
如果不写清楚,譬如这段代码,
1 2 3 4 fn double (x: i32 ) -> i32 {x * 2 } print! ("{}" , double (7i32 ));
1 2 3 4 fn double (x: i32 ) -> i32 {x * 2 } print! ("{}" , 7i32 .double ());
这里的点操作调用,会发生编译错误,它会说当前范围内,i32类型没有double方法。区别于方法和函数,Rust中点操作的调用,区分为方法,它仅能在有trait实现的声明的方法中调用,所以,要允许点操作,可以改为,
1 2 3 4 5 6 7 8 9 trait CanBeDoubled { fn double (self ) -> Self ; } impl CanBeDoubled for i32 { fn double (self ) -> Self { self * 2 } } print! ("{}" , 7i32 .double ());
trait的名字是任意的。通常trait仅包含一个函数,trait的名字使用Pascal-case记法。对于类型来说,像CanBeDoubled,从命名上看出,它表示有一个double函数可以获取自身self类型的一个值,遵循这种命名规范便于阅读理解。
当编译这段表达式时,编译器会搜索支持i32的double操作,并找到对应的方法签名。
The “self” and “Slef” Keywords
前面一个小节,我们发现了两个关键字:“self”和“Self”。
在语句trait CanBeDoubled { fn double(self) -> Self; }中,self表示double方法将作用的值,Self表示self的类型。
因此,self是一个方法的预设参数,Self表示这一个参数的类型。因此,self和Self仅能被用于一个trait或impl的块内。以及,如果有方法,self必须是方法的第一个参数。
在impl CanBeDoubled for i32块内,下面6行是等价的:
1 2 3 4 5 6 fn double (self ) -> Self {fn double (self : Self ) -> Self {fn double (self : i32 ) -> Self {fn double (self ) -> i32 {fn double (self : Self ) -> i32 {fn double (self : i32 ) -> i32 {
第一行和第四行给定的self参数带有隐式类型;只不过,self的类型就是Self,所以也可以显式指定,又因为在impl块内,Self就是i32,所以也可以替换为i32。
不过最常使用的是第一种写法,它更接近泛型编程概念。
让我们看看另一种情况,我们希望有这样一个表达式"foobarbaz".letters_count('a')"统计字符串中有多少个字符,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 trait LettersCount { fn letters_count (&self , ch: char ) -> usize ; } impl LettersCount for str { fn letters_count (&self , ch: char ) -> usize { let mut count = 0 ; for c in self .chars () { if c == ch { count += 1 ; } } count } } print! ("{} " , "" .leters_count('a' ));print! ("{} " , "ddd" .leters_count('a' ));print! ("{} " , "ddd" .leters_count('d' ));print! ("{} " , "foobarbaz" .leters_count('a' ));
因为我们想用点操作,首先声明一个trait,它的名字来源于函数名。这个函数需要两个参数:字符串切片用于搜索,字符用于查找。但我们不想将字符串切片的拷贝作为参数传递;我们仅想直接传递字符串切片引用,因此我们将参数声明为&self,这里的self就是一个字符串切片,有任意长度;&self是一个切片引用,有一对指针的大小(字符串切片有header和content的pointer)。
返回值类型是usize表示非负整数。
impl实现了使用了命令式风格。浏览chars()迭代器的所有字符,出现要搜索的字符,则统计一次。
如果使用函数式风风格,可以更简短,如下,
1 self .chars ().filter (|c| *c == ch).count ()
Standard Traits
在最开始的章节,我们用到了宏print、println和format。我们可以用{}占位符表示支持的类型,使用{:?}来进行调试。
但怎么知道某些类型支持{}占位符,其它类型却不支持?我自己写的类型如何实现支持这种占位符?
实际上,这些宏使用了fmt函数,有标准库的std::fmt::Display提供了trait。所有原生类型都实现了这个trait,所以你可以给自己的类型实现,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 struct Complex { re: f64 , im: f64 , } impl std ::fmt::Display for Compex { fn fmt (&self , &mut std::fmt::Formatter) -> std::fmt::Result { write! ( f, "{} {} {}i" , self .re, if self .im >= 0 . { '+' } else { '-' }, self .im.abs () ) } } let c1 = Complex { re: -2.3 , im: 0 . };let c2 = Complex { re: -2.1 , im: -5.2 };let c3 = Complex { re: -2.2 , im: 5.2 };print! ("{}, {}, {}" , c1, c2, c3);
结果会打印:“-2.3 + 0i, -2.1 - 5.2i, -2.2 + 5.2i”。
对于原生类型的实现traits,标准库中有非常多函数提供。
The “Terator” Trait
一个非常有趣的标准库trait是“Iterator”。让我们看看它主要解决哪方面问题。
例如编写一个函数,给定参数range,返回第三个元素,长度不够,则返回None。
1 2 3 4 5 6 7 8 fn get_third (r: std::ops::Range<u32 >) -> Option <u32 > { if r.len () >= 3 { Some (r.start + 2 ) } else { None } } print! ("{:?} {:?}" , get_third (10 ..12 ), get_third (20 ..23 ));
将类型换成slice怎样,
1 2 3 4 5 6 7 8 fn get_third (s: &[f64 ]) -> Option <f64 > { if s.len () >= 3 { Some (s[2 ]) } else { None } } print! ("{:?} {:?}" , get_third (&[1.0 , 2.0 ]), get_thrid (&[1.1 , 2.1 , 3.1 ]);
这两个程序非常相似。但使用的是迭代器,应该将它们写成一个泛型函数,你可能会写成
1 2 3 4 5 6 fn get_third <Iter, Item>(mut iterator: Iter) -> Option <Item> { iterator.next (); iterator.next (); iterator.next () } print! ("{:?} {:?}" , get_third (0 ..9 ), get_third ([11 , 22 , 33 , 44 ].iter ()));
你会得到几个编译错误。这种想法是好的,但有几个问题,
iterator变量没有边界,所以它没有next函数。当我们调用get_third函数式,我们看到参数确实是iterator,以为有next函数。然而,Rust需要知道泛型参数对象有哪些函数可以被调用,
再看get_thrid函数的调用,它的泛型参数Item不能被推断,因为没有表达式表明给这个泛型参数传递了值。
对于第一种错误,表明“迭代器”的概念没有被Rust语言定义。这个概念由Rust标准库的一个标准trait——Iterator定义了。我们知道迭代器都有一个next函数,所以任何迭代器都必须要有这个函数。
1 2 fn get_third <Iter, Item>(mut iterator: Iter) -> Option <Iterm> where Iter: std::iter::Iterator {
但仍然有第二个错误存在:怎么确定Item的具体类型。为了解决这个问题,需要首先介绍type关键字。
The “type” Keyword
Rust中的type对于C语言的typedef关键字,它相当于一个类型的别名,
1 2 3 4 5 6 type Number = f32 ;fn f1 (x: Number) -> Number { x }fn f2 (x: Number) -> NUmber { x }let a : Number = 2.3 ;let b : Number = 3.4 ;print! ("{} {}" , f1 (a), f2 (b));
使用type结构有两点好处:
简洁代码,它使用了一个有意义的名字来表示原生类型了
方便性,不用频繁切换类型,只需要修改type的类型即可
但type实际上有另一个重要用途,
Generic Traits
前面的章节我们知道有泛型函数和泛型结构体。trait也可以由一个或多个类型参数化表示,即要求它的函数需要泛型参数的情况。这个概念和Java的接口类似,
1 2 3 4 5 6 7 trait Searchable <Key> { fn contains (&self , key: Key) -> bool ; } fn is_present <Collection>(coll: &Collection, id: u32 ) -> bool where Collection: Searchable<u32 > { coll.contains (id) }
下面是该代码的完整实现,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 trait Searchable <Key> { fn contains (&self , key: Key) -> bool ; } struct RecordWithId { id: u32 , _descr: String , } struct NameSetWithId { data: Vec <RecordWithId> } impl Searchable <u32 > for NameSetWithId { fn contains (&self , key: u32 ) -> bool { for record in self .data.iter () { if record.id == key { return true ; } } false } } fn is_present <Collection>(coll: &Collection, id: u32 ) -> bool where Collection: Searchable<u32 > { coll.contains (id) } let names = NameSetWithId { data: vec! [ RecordWithId { id: 34 , _descr: "John" .to_string (), }, RecordWithId { id: 49 , _descr: "Jane" .to_string (), }, ], }; print! ("{}, {}" , is_present (&names, 48 ), is_present (&names, 49 ));
声明了Searchable泛型trait后,也声明了两个结构体:“RecordWithId”,表示由唯一数字标识的数据元素;“NameSetWithId”,表示一个类型为RecordWithId的集合。
然后,trait实现了这个集合类型。有两个方式实现:保留泛型参数,编写类似于impl<T> Searchable<T> for NameSetWithId {;这里是另一种实现方法,因为contains不仅需要指定NameSetWithId,还需要知道Key的具体类型。
定义了is_present函数,要是有这个函数,需要定义对应的结构体。
这个解决方案虽然生效了,但有一些缺陷。
这里,Searchable需要指定Key的类型是u32,另外还要指定参数化类型的值,但在where从句中又重复指定了一次,
考虑更复杂的情况,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 trait Searchable <Key, Count> { fn contains (&self , key: Key) -> bool ; fn count (&self , key: Key) -> Count; } struct RecordWithId { id: u32 , _descr: String , } struct NameSetWithId { data: Vec <RecordWithId>, } impl Searchable <u32 , usize > for NameSetWithId { fn contains (&self , key: u32 ) -> bool { for record in self .data.iter () { if record.id == key { return true ; } } false } fn count (&self , key: u32 ) -> usize { let mut c = 0 ; for record in self .data.iter () { if record.id == key { c += 1 ; } } c } } fn is_present <Collection>(coll: &Collection, id: u32 ) -> bool where Collection: Searchable<u32 , usize >, { coll.contains (id) } let names = NameSetWithId { data: vec! [ RecordWithId { id: 34 , _desrc: "John" .to_string (), }, RecordWithId { id: 49 , _desrc: "Jane" .to_string (), ], }; print! ( "{}, {}; {} {}" , names.count (48 ), names.count (49 ), is_present (&names, 48 ), is_present (&names, 49 ), );
这里不明显地is_present的泛型函数签名,必须指定新的类型。但这个函数并没有使用这个类型,这个类型参数在这里没有很大意义。
Using Associated Types to Simplify Generic Traits Use
前面以及描述了这个无实际意义的泛型参数签名的问题。一个最好的解决方案如下,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 trait Searchable { type Key ; type Count ; fn contains (&self , key: Self ::Key) -> bool ; fn count (&self , key: Self ::Key) -> Self ::Count; } struct RecordWithId { id: u32 , _desrc: String , } struct NameSetWithId { data: Vec <RecordWithId>, } impl Searchable for NameSetWithId { type Key = u32 ; type Count = usize ; fn contains (&self , key: Self ::Key) -> bool { for record in self .data.iter () { if record.id == key { return true ; } } false } fn count (&self , key: Self ::Key) -> usize { let mut c = 0 ; for record in self .data.iter () { if record.id == key { c += 1 ; } } c } } fn is_present <Collection>( coll: &Collection, id: <Collection as Searchable>::Key, ) -> bool where Collection: Searchable, { coll.contains (id) } let names = NameSetWithId { data: vec! [ RecordWithId { id: 34 , _desrc: "John" .to_string (), }, RecordWithId { id: 49 , _desrc: "Jane" .to_string (), }, ], }; print! ("{}, {}; {} {}" , names.count (48 ), names.count (49 ), is_present (&names, 48 ), is_present (&names, 49 ));
首先是,“Searchable”特质不再使用泛型,而是将泛型定义在自身内部,
这点写法和Scala的trait真的非常非常像…
因此,每次使用“Key”和“Count”类型参数时,都需要带前缀“Self::”。
这些改变的好处体现在is_present方法签名上。首先如其用具体的类型,这里用一个关联类型Key指定,这个不需要在指定Searchable,因为它没有泛型。
这里将类型的定义绑定在一个trait上,这种实现机制,对于大型软件开发更有优势。
the “Iterator” Standard Trait Declaration
关于“Iterator”这个标准trait,我们说过它仅包含一个item:next函数签名。这样说不对的。应该说是一个泛型的item。
因为它由type元素签名,你可以认为它在标准库中的定义是这样的,
1 2 3 4 trait Iterator { type Item ; fn next (&mut self ) -> Option <Self ::Item>; }
这种定义强制要求具体的迭代实现要为Item类型定义,以及实现next的方法体,
下面是一种可能的实现range的方式,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 trait MyIterator { type Item ; fn next (&mut self ) -> Option <Self ::Item>; } struct MyRangeIterator <T> { current: T, limit: T, } impl MyIterator for MyRangeIterator <u32 > { type Item = u32 ; fn next (&mut self ) -> Option <Self ::Item> { if self .current == self .limit { None } else { self .current += 1 ; Some (self .current - 1 ) } } } let mut range_it = MyRangeIterator { current: 10 , limit: 13 , }; print! ("{:?}, " , range_it.next ());print! ("{:?}, " , range_it.next ());print! ("{:?}, " , range_it.next ());print! ("{:?}, " , range_it.next ());print! ("{:?}, " , range_it.next ());print! ("{:?}, " , range_it.next ());
这里使用了MyRangeIterator<u32>,指定了它的具体类型,但实际上,我们不需要定义MyIterator特质,因为我们可以直接使用标准库的Iterator。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 struct MyRangeIterator <T> { current: T, limit: T, } impl Iterator for MyRangeIterator <u32 > { type Item = u32 ; fn next (&mut self ) -> Option <Self ::Item> { if self .current == self .limit { None } else { self .current += 1 ; Some (self .current - 1 ) } } } print! ("{:?}; " , MyRangeIterator { current: 10 , limit: 13 , }.collect::<Vec <_>>() ); for i in (MyRangeIterator { current: 20 , limit: 24 , }) { print! ("{} " , i); }
因为“MyRangeIterator”对象有实现“Iterator”特质的类型,所以它可以使用collect迭代器消费者。
Using Generic Iterators
现在,回到原先那个Item无用的问题。我们想要实现泛型函数get_third,它接收任何迭代器,返回迭代器第三个元素,问题可以由下面代码解决,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 fn get_third <Iter>(mut iterator: Iter) -> Option <Iter::Item>where Iter: std::iter::Iterator , { iterator.next (); iterator.next (); iterator.next () } print! ( "{:?} {:?} {:?} {:?}" , get_third (10 ..12 ), get_third (20 ..29 ), get_third ([31 , 32 ].iter ()), get_third ([41 , 42 , 43 , 44 ].iter ()) );
这里直接用where来绑定的Item的范围,这样一来,就可以访问Item关联的类型了,包括它的方法next。以及它的返回类型就是Option<Iter::Item>。
这段代码,对于迭代参数类型的函数编写提供了参考。实际上,标准库中已经定义了类型的迭代器消费者nth。所以下面写法是等价的,
1 2 3 4 5 6 7 print! ( "{:?} {:?} {:?} {:?}" , (10 ..12 ).nth (2 ), (20 ..29 ).nth (2 ), ([31 , 32 ].iter ()).nth (2 ), ([41 , 42 , 43 , 44 ].iter ()).nth (2 ) );