Movatterモバイル変換


[0]ホーム

URL:


codecamp

特征

特征

你还记得 impl 关键字吗,它用于调用一个函数的methodsyntax

struct Circle {x: f64,y: f64,radius: f64,}impl Circle {fn area(&self) -> f64 {std::f64::consts::PI * (self.radius * self.radius)}}

特征几乎都是相似的,除了我们用方法签名去定义一个特征,然后实现该结构的特征。如下面所示:

struct Circle {x: f64,y: f64,radius: f64,}trait HasArea {fn area(&self) -> f64;}impl HasArea for Circle {fn area(&self) -> f64 {std::f64::consts::PI * (self.radius * self.radius)}}

如您所见,这个特征块和 impl 块非常相似,但我们不定义一个主体,只是定义一个类型签名。当我们 impl 一个特征时,我们使用 impl 特征项,而不仅仅是 impl 项。

我们可以使用特征约束泛型。思考下面这个函数,它没有编译,只给我们一个类似的错误:

fn print_area<T>(shape: T) {println!("This shape has an area of {}", shape.area());}

Rust 可能会抱怨道:

error: type `T` does not implement any method in scope named `area`

因为 T 可以是任何类型,我们不能确保它实现了 area 的方法。但我们可以添加一个特征约束的泛型 T,确保它已经实现:

fn print_area<T: HasArea>(shape: T) {println!("This shape has an area of {}", shape.area());}

语法< T:HasArea > 意味着实现 HasArea 特征的任何类型。因为特征定义函数的类型签名,我们可以肯定,任何实现 HasArea 的类型都会有.area()方法。

这里有一个扩展的例子展示它是如何工作的:

trait HasArea {fn area(&self) -> f64;}struct Circle {x: f64,y: f64,radius: f64,}impl HasArea for Circle {fn area(&self) -> f64 {std::f64::consts::PI * (self.radius * self.radius)}}struct Square {x: f64,y: f64,side: f64,}impl HasArea for Square {fn area(&self) -> f64 {self.side * self.side}}fn print_area<T: HasArea>(shape: T) {println!("This shape has an area of {}", shape.area());}fn main() {let c = Circle {x: 0.0f64,y: 0.0f64,radius: 1.0f64,};let s = Square {x: 0.0f64,y: 0.0f64,side: 1.0f64,};print_area(c);print_area(s);}

这个程序输出:

This shape has an area of 3.141593This shape has an area of 1

如您所见,print_area 现在是通用的,同时也确保我们通过了正确的类型。如果我们通过一个错误的类型:

print_area(5);

我们会得到一个编译错误:

error: failed to find an implementation of trait main::HasArea for int

到目前为止,我们只添加特征实现结构,但您可以实现任何类型的特征。所以从技术上讲,我们可以为 i32 实现 HasArea:

    trait HasArea {fn area(&self) -> f64;}impl HasArea for i32 {fn area(&self) -> f64 {println!("this is silly");*self as f64}}5.area();

我们一般认为用这种基本类型来实现方法是一种不够好的风格,即便这种实现方法是可行的。

这可能看起来比较粗糙,但是有两个其他的限制来控制特征的实现,防止失控。首先,如果特征不是在你的范围中定义的,那么不适用。下面是一个例子:标准库提供了编写特征,这个特征给文件的输入输出增加了额外的功能。默认情况下,文件并不会有自己的方法:

let mut f = std::fs::File::open("foo.txt").ok().expect("Couldn’t open foo.txt");let result = f.write("whatever".as_bytes());

这里有一个错误:

error: type `std::fs::File` does not implement any method in scope named `write`let result = f.write(b"whatever");

我们需要先使用编写特征:

use std::io::Write;let mut f = std::fs::File::open("foo.txt").ok().expect("Couldn’t open foo.txt");let result = f.write("whatever".as_bytes());

此时编译没有出现错误。

这意味着,即使有人做了坏事比如向 int 添加方法,也不会影响你,除非你使用这个特征。

实现特征还有其他限制。你为特征或类型所写的 impl 必须由你来定义。所以,我们可以实现 HasArea 类型等,因为 HasArea 是我们的代码。但是如果我们试图实现浮点型,它是 Rust 为 i32 所提供的特征,是不可能的,因为无论特征还是类型都不在我们的代码里面。

最后关于特征:通用函数特征的捆绑使用 “monomorphization”(mono:一个,morph:形式),所以他们都是静态调用。那意味着什么呢?到特征对象那一章查看更多细节。

多个特征边界

如您所看到的,您可以绑定特征与一个泛型的类型参数:

fn foo<T: Clone>(x: T) {x.clone();}

如果你需要多个绑定,您可以使用+:

use std::fmt::Debug;fn foo<T: Clone + Debug>(x: T) {x.clone();println!("{:?}", x);}

T 现在需要复制以及调试。

Where 子句

只用少数泛型和少量的特征界限来写函数并不是太糟糕,但随着数量的增加,语法变得越来越糟糕:

use std::fmt::Debug;fn foo<T: Clone, K: Clone + Debug>(x: T, y: K) {x.clone();y.clone();println!("{:?}", y);}println !(“{:?}”,y);}

函数的名称是在最左边,参数列表在最右边。边界就以这种方式存在。

Rust 已经有了解决方法,这就是所谓的“where 子句”:

use std::fmt::Debug;fn foo<T: Clone, K: Clone + Debug>(x: T, y: K) {x.clone();y.clone();println!("{:?}", y);}fn bar<T, K>(x: T, y: K) where T: Clone, K: Clone + Debug {x.clone();y.clone();println!("{:?}", y);}fn main() {foo("Hello", "world");bar("Hello", "workd");}

foo()使用我们之前讲解的语法,bar()使用一个 where 子句。所有你需要做的就是定义类型参数时不要定义边界,然后在参数列表之后添加 where 语句。对于更长的列表,可以添加空格:

use std::fmt::Debug;fn bar<T, K>(x: T, y: K)where T: Clone,  K: Clone + Debug {x.clone();y.clone();println!("{:?}", y);}

这种灵活性可以使复杂情况变得清晰。

Where 语句也比简单的语法更加强大。例如:

trait ConvertTo<Output> {fn convert(&self) -> Output;}impl ConvertTo<i64> for i32 {fn convert(&self) -> i64 { *self as i64 }}// can be called with T == i32fn normal<T: ConvertTo<i64>>(x: &T) -> i64 {x.convert()}// can be called with T == i64fn inverse<T>() -> T// this is using ConvertTo as if it were "ConvertFrom<i32>"where i32: ConvertTo<T> {1i32.convert()}

这个例子展了示 where 子句的附加特性:它们允许范围内的左边可以是一个任意的类型(在这里是 i32),而不只是一个普通的类型参数(如 T)。

默认的方法

我们的最后一个特征的特性应包括:默认的方法。举个简单的例子更容易说明:

trait Foo {fn bar(&self);fn baz(&self) { println!("We called baz."); }}

Foo 特征的实现者需要实现 bar()方法,但是他们不需要实现 baz()。他们会得到这种默认行为。如果他们这么选择的话就可以覆盖默认的情形:

struct UseDefault;impl Foo for UseDefault {fn bar(&self) { println!("We called bar."); }}struct OverrideDefault;impl Foo for OverrideDefault {fn bar(&self) { println!("We called bar."); }fn baz(&self) { println!("Override baz!"); }}let default = UseDefault;default.baz(); // prints "We called baz."let over = OverrideDefault;over.baz(); // prints "Override baz!"⇱

继承

有时,想要实现某个特征需要先实现另一个特征:

trait Foo {fn foo(&self);}trait FooBar : Foo {fn foobar(&self);} FooBar的实现者也要实现Foo,像这样:struct Baz;impl Foo for Baz {fn foo(&self) { println!("foo"); }}impl FooBar for Baz {fn foobar(&self) { println!("foobar"); }}

如果我们忘记实现 Foo,Rust 会告诉我们:

error: the trait `main::Foo` is not implemented for the type `main::Baz` [E027]
泛型
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录
关于
介绍

新手入门

学习 Rust

高效 Rust

语法和语义

Nightly Rust

词汇表
相关学术研究

关闭

MIP.setData({'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false},'pageFontSize' : getCookie('pageFontSize') || 20});MIP.watch('pageTheme', function(newValue){setCookie('pageTheme', JSON.stringify(newValue))});MIP.watch('pageFontSize', function(newValue){setCookie('pageFontSize', newValue)});function setCookie(name, value){var days = 1;var exp = new Date();exp.setTime(exp.getTime() + days*24*60*60*1000);document.cookie = name + '=' + value + ';expires=' + exp.toUTCString();}function getCookie(name){var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)');return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null;}
[8]ページ先頭

©2009-2025 Movatter.jp