结构体泛型与函数泛型 #
struct Point<T, U> {
x: T,
y: U,
}
impl<T, U> Point<T, U> {
fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
Point {
x: self.x,
y: other.y,
}
}
}
fn main() {
let p1 = Point { x: 5, y: 10.4 };
let p2 = Point { x: "Hello", y: 'c' };
let p3 = p1.mixup(p2);
println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}
T,U 是定义在结构体 Point 上的泛型参数, V,W 是单独定义在方法 mixup 上的泛型参数,并不冲突
针对特定的泛型类型实现某个特定的方法,对于其它泛型类型则没有定义该方法。
impl Point<f32> {
fn distance_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
const 泛型 #
使用数组切片,然后传入 arr 的不可变引用,避免两个完全不相同类型无法使用同一个函数的方法调用问题
fn display_array<T: std::fmt::Debug, const N: usize>(arr: [T; N]) {
println!("{:?}", arr);
}
fn main() {
let arr: [i32; 3] = [1, 2, 3];
display_array(arr);
let arr: [i32; 2] = [1, 2];
display_array(arr);
}
定义一个类型为 [T; N] 的数组,其中 T 是一个基于类型的泛型参数, N 这个泛型参数,它是一个基于值的泛型参数!因为它用来替代的是数组的长度。 N 就是 const 泛型,定义的语法是 const N: usize ,表示 const 泛型 N ,它基于的值类型是usize 。
泛型的性能 #
泛型是零成本的抽象,意味着你在使用泛型时,完全不用担心性能上的问题. 但是Rust 是在编译期为泛型对应的多个类型,生成各自的代码,因此损失了编译速度和增大了最终生成文件的大小。
Rust 通过在编译时进行泛型代码的 单态化(monomorphization)来保证效率。单态化是一个通过填充编译时使用的具体类型,将通用代码转换为特定代码的过程。
let integer = Some(5);
let float = Some(5.0);
enum Option_i32 {
Some(i32),
None,
}
enum Option_f64 {
Some(f64),
None,
}
fn main() {
let integer = Option_i32::Some(5);
let float = Option_f64::Some(5.0);
}
- 当 Rust 编译这些代码的时候,它会进行单态化。编译器会读取传递给 Option<T> 的值并发现有两种Option<T> :
- 一种对应 i32 另一种对应 f64 。为此,它会将泛型定义 Option<T> 展开为Option_i32 和 Option_f64
- 接着将泛型定义替换为这两个具体的定义。
可以使用泛型来编写不重复的代码,而 Rust 将会为每一个实例编译其特定类型的代码。
这意味着在使用泛型时没有运行时开销;当代码运行,它的执行效率就跟好像手写每个具体定义的重复代码一样。
这个单态化过程正是 Rust 泛型在运行时极其高效的原因。
特征 Trait #
特征定义了一组可以被共享的行为,只要实现了特征,你就能使用这组行为。
#[derive(Debug)] ,它在我们定义的类型
( struct )上自动派生 Debug 特征,接着可以使用 println!("{:?}", x) 打印这个类型
fn add<T: std::ops::Add<Output = T>>(a: T, b: T) -> T {
a + b
}
特征只定义行为看起来是什么样的,而不定义行为具体是怎么样的。因此,我们只定义特征方法的签名,而不进行实现,此时方法签名结尾是 ; ,而不是一个 {} 。
每一个实现这个特征的类型都需要具体实现该特征的相应方法,编译器也会确保任何实现Summary 特征的类型都拥有与这个签名的定义完全一致的 summarize 方法
pub trait Summary {
fn summarize(&self) -> String;
}
pub struct Post {
pub title: String, // 标题
pub author: String, // 作者
pub content: String, // 内容
}
impl Summary for Post {
fn summarize(&self) -> String {
format!("文章{}, 作者是{}", self.title, self.author)
}
}
pub struct Weibo {
pub username: String,
pub content: String,
}
impl Summary for Weibo {
fn summarize(&self) -> String {
format!("{}发表了微博{}", self.username, self.content)
}
}
fn main() {
let post = Post {
title: "Rust语言简介".to_string(),
author: "Sunface".to_string(),
content: "Rust棒极了!".to_string(),
};
let weibo = Weibo {
username: "sunface".to_string(),
content: "好像微博没Tweet好用".to_string(),
};
println!("{}", post.summarize());
println!("{}", weibo.summarize());
}
特征定义与实现的位置(孤儿规则) #
如果你想要为类型 A 实现特征 T ,那么 A或者 T 至少有一个是在当前作用域中定义的!
weibo.summarize() 会先调用 Summary 特征默认实现的 summarize 方法,通过该方法进而调用
Weibo 为 Summary 实现的 summarize_author 方法,最终输出: 1 new weibo: (Read more
from @horse_ebooks...) 。
pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
impl Summary for Weibo {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
}
pub struct Weibo {
pub username: String,
pub content: String,
}
fn main() {
let weibo = Weibo {
username: "sunface".to_string(),
content: "好像微博没Tweet好用".to_string(),
};
println!("{}", weibo.summarize());
}
使用特征作为函数参数
#
// 实现了 Summary 特征 的 item 参数
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
使用任何实现了 Summary 特征的类型作为该函数的参数,同时在函数体内,还可以调用该特征的方法 可以传递 Post 或 Weibo 的实例来作为参数,而其它类如 String 或者 i32 的类型则不能用做该函数的参数,因为它们没有实现 Summary 特征
特征约束(trait bound)
#
pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
# T: Summary 被称为特征约束
一个函数接受两个 impl Summary 的参数,如果函数两个参数是不同的类型可使用
pub fn notify(item1: &impl Summary, item2: &impl Summary) {}
如果不同上面的语法就无法做到这种限制。 使用特征约束,泛型类型 T 说明了 item1 和 item2 必须拥有同样的类型,同时 T: Summary 说明了 T 必须实现Summary 特征。
pub fn notify<T: Summary>(item1: &T, item2: &T) {}
除了单个约束条件,我们还可以指定多个约束条件,例如除了让参数实现 Summary 特征外,还可以让参数实现 Display 特征以控制它的格式化输出
多重约束
pub fn notify(item: &(impl Summary + Display)) {}
pub fn notify<T: Summary + Display>(item: &T) {}
Where 约束 当特征约束变得很多时,函数的签名将变得很复杂:
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {}
对其做一些形式上的改进,通过 where
fn some_function<T, U>(t: &T, u: &U) -> i32
where
T: Display + Clone,
U: Clone + Debug,
{
}
函数返回中的 impl Trait #
通过 impl Trait 来说明一个函数返回了一个类型,该类型实现了某个特征,这种返回值方式有一个很大的限制:只能有一个具体的类型
fn returns_summarizable() -> impl Summary {
Weibo {
username: String::from("sunface"),
content: String::from("m1 max太厉害了,电脑再也不会卡"),
}
}
例子:让 T 拥有 Copy 特性,增加特征约束
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number is {}", result);
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);
println!("The largest char is {}", result);
}
通过 derive 派生特征 #
- 被derive 标记的对象会自动实现对应的默认特征代码,继承相应的功能。
- Debug 特征,它有一套自动实现的默认代码,当你给一个结构体标记后,就可以使用println!("{:?}", s) 的形式打印该结构体的对象。
- Copy 特征,它也有一套自动实现的默认代码,当标记到一个类型上时,可以让这个类型自动实现 Copy 特征,进而可以调用 copy 方法,进行自我复制。
- derive 派生出来的是 Rust 默认给我们提供的特征,在开发过程中极大的简化了自己手动实现相应特征的需求
调用方法需要引入特征 #
使用一个特征的方法,那么你需要将该特征引入当前的作用域中,但是 Rust 又提供了一个非常便利的办法,即把最常用的标准库中的特征通过 std::prelude 模块提前引入到当前作用域中,其中包括了 std::convert::TryInto 。
// 移除也OK
use std::convert::TryInto;
fn main() {
let a: i32 = 10;
let b: u16 = 100;
let b_ = b.try_into().unwrap();
if a < b_ {
println!("Ten is less than one hundred.");
}
}
use std::ops::Add;
// 为Point结构体派生Debug特征,用于格式化输出
#[derive(Debug)]
struct Point<T: Add<T, Output = T>> {
// Output是Add trait的一部分,它定义了Add操作的结果类型。
//限制类型T必须实现了Add特征,否则无法进行+操作。
x: T,
y: T,
}
impl<T: Add<T, Output = T>> Add for Point<T> {
type Output = Point<T>;
fn add(self, p: Point<T>) -> Point<T> {
Point {
x: self.x + p.x,
y: self.y + p.y,
}
}
}
fn add<T: Add<T, Output = T>>(a: T, b: T) -> T {
a + b
}
fn main() {
let p1 = Point {
x: 1.1f32,
y: 1.1f32,
};
let p2 = Point {
x: 2.1f32,
y: 2.1f32,
};
println!("{:?}", add(p1, p2));
let p3 = Point { x: 1i32, y: 1i32 };
let p4 = Point { x: 2i32, y: 2i32 };
println!("{:?}", add(p3, p4));
}
例子:
/*
在Rust中,#![allow(dead_code)]和#[allow(dead_code)]都是属性(attributes)用来禁用未使用代码的警告,但它们的应用范围不同。
#![allow(dead_code)]是一个内部属性(inner attribute),它应用于紧随其后的项。在大多数情况下,它被放在文件的顶部,影响整个文件中的所有项。
#[allow(dead_code)]是一个外部属性(outer attribute),它只应用于紧随其后的单个项(如函数、结构体、模块等)。
*/
#![allow(dead_code)] // 禁止对未使用的代码的警告。
use std::fmt; // 格式化输出
use std::fmt::Display; // 格式化输出
#[derive(Debug, PartialEq)] // 自动为枚举生成Debug和PartialEq trait的实现
enum FileState {
Open,
Closed,
}
#[derive(Debug)]
struct File {
name: String,
data: Vec<u8>,
state: FileState,
}
impl Display for FileState {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
FileState::Open => write!(f, "OPEN"),
FileState::Closed => write!(f, "CLOSED"),
}
}
}
impl Display for File {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "<{} ({})>", self.name, self.state)
}
}
impl File {
fn new(name: &str) -> File {
File {
name: String::from(name),
data: Vec::new(),
state: FileState::Closed,
}
}
}
fn main() {
let f6 = File::new("f6.txt");
println!("{:?}", f6);
println!("{}", f6);
}