rust学习-泛型和trait

这篇具有很好参考价值的文章主要介绍了rust学习-泛型和trait。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

泛型

Option,Vec,HashMap<K, V>,Result<T, E>等,取函数以减少代码重复的机制

背景

两个函数,不同点只是名称和签名类型

fn largest_i32(list: &[i32]) -> i32 {
    let mut largest = list[0];

    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn largest_char(list: &[char]) -> char {
    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_i32(&number_list);
    println!("The largest number is {}", result);

    let char_list = vec!['y', 'm', 'a', 'q'];

    let result = largest_char(&char_list);
    println!("The largest char is {}", result);
}

重写如下

fn largest<T>(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);
}

// 编译失败,提示受限的类型,因为有的类型没法用
// help: consider restricting type parameter `T`
// fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> T
// binary operation `>` cannot be applied to type `T`
// 具体的解法见下文

结构体的泛型

struct Point<T, U> {
    x: T,
    y: U,
}

fn main() {
    let both_integer = Point { x: 5, y: 10 };
    let both_float = Point { x: 1.0, y: 4.0 };
    let integer_and_float = Point { x: 5, y: 4.0 };
}

枚举中的泛型

// Some,它存放了一个类型 T 的值、不存在任何值的 None
enum Option<T> {
    Some(T),
    None,
}

// Result 枚举有两个泛型类型,T 和 E
// Result 有两个成员:Ok,它存放一个类型 T 的值,而 Err 则存放一个类型 E 的值
// 如当成功打开文件的时候,T 对应的是 std::fs::File 类型
// 比如当打开文件出现问题时,E 的值则是 std::io::Error 类型
enum Result<T, E> {
    Ok(T),
    Err(E),
}

结构体的泛型

为所有类型的结构体提供方法

struct Point<T> {
    x: T,
    y: T,
}

// 在为结构体和枚举实现方法时,都可以使用泛型
impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

fn main() {
    let p = Point { x: 5, y: 10 };

    println!("p.x = {}", p.x());
}

只为f32提供方法

struct Point<T> {
    x: T,
    y: T,
}

impl Point<f32> {
    fn distance_from_origin(&self) -> f32 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

fn main() {
    // 打开如下两行,编译报错
    // method `distance_from_origin` not found for this struct
    // let p = Point { x: 5, y: 10 };
    // println!("result={}", p.distance_from_origin());

    // 如下两行则正常编译
	 let p = Point { x: 5.1, y: 10.2 };
	 println!("result={}", p.distance_from_origin());
}

方法使用了与结构体定义中不同类型的泛型

struct Point<T, U> {
    x: T,
    y: U,
}

impl<T, U> Point<T, U> {
    // 泛型参数 V 和 W 声明于 fn mixup 后,因为他们只是相对于方法本身
    // 删除之后编译失败:cannot find type `V、W` in this scope
    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);
}

泛型是否会影响运行时性能

Rust 实现了泛型,使得使用泛型类型参数的代码相比使用具体类型并没有任何速度上的损失
编译时进行泛型代码的单态化:通过填充编译时使用的具体类型,将通用代码转换为特定代码的过程

enum Option_i32 {
    Some(i32),
    None,
}

enum Option_f64 {
    Some(f64),
    None,
}

fn main() {
    // 将泛型定义 Option<T> 展开为 Option_i32 和 Option_f64
    let integer = Option_i32::Some(5);
    let float = Option_f64::Some(5.0);
}

trait

trait,定义泛型行为的方法
可以与泛型结合来将泛型限制为拥有特定行为的类型,而不是任意类型
类似于其他语言的接口

示例

为NewsArticle和Tweet实现相同的摘要接口

// trait
pub trait Summary {
    fn summarize(&self) -> String;
}

// 在相同的 lib.rs 里定义了 Summary trait 和 NewsArticle 与 Tweet 类型
// 他们位于同一作用域
pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

// 为不同的类型实现不同的summarize
impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

// 为不同的类型实现不同的summarize
impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

let tweet = Tweet {
    username: String::from("horse_ebooks"),
    content: String::from("of course, as you probably already know, people"),
    reply: false,
    retweet: false,
};

println!("new tweet: {}", tweet.summarize());

trait复用

别人想要用该crate的功能为其自己的库作用域中的结构体实现 Summary trait:
(1)需要将 trait 引入作用域:use aggregator::Summary;
(2)Summary 必须是公有 trait, 其他 crate 才可以实现它,将 pub 置于 trait 之前

只有当 trait 或者要实现 trait 的类型位于 crate 的本地作用域时,才能为该类型实现 trait:
(1)为当前crate 的自定义类型 Tweet 实现如标准库中的 Display trait,Tweet 类型位于本地的作用域
(2)在 当前crate 中为 Vec 实现 Summary,Summary trait 位于 该crate 本地作用域

不能为外部类型实现外部 trait:
(1)不能在 aggregator crate 中为 Vec 实现 Display trait,Display 和 Vec 都定义于标准库中,不在当前作用域

专属词汇:这个限制是被称为 相干性(coherence) 的程序属性的一部分,或者更具体的说是 孤儿规则(orphan rule),忽略即可

trait默认实现

pub trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}

如果想要对 NewsArticle 实例使用这个默认实现,而不是定义一个自己的实现,则可以通过 impl Summary for NewsArticle {} 指定一个空的 impl 块。
为 summarize 创建默认实现并不要求对Tweet 上的 Summary 实现做任何改变。其原因是(重载一个默认实现的语法)与(实现没有默认实现的 trait 方法)的语法一样

trait提供很多接口但只实现部分

pub trait Summary {
    fn summarize_author(&self) -> String;

    fn summarize(&self) -> String {
        format!("(Read more from {}...)", self.summarize_author())
    }
}

// 为了使用这个版本的 Summary
// 只需在实现 trait 时定义 summarize_author
impl Summary for Tweet {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }
}

// 具体实现
let tweet = Tweet {
    username: String::from("horse_ebooks"),
    content: String::from("of course, as you probably already know, people"),
    reply: false,
    retweet: false,
};
println!("1 new tweet: {}", tweet.summarize());

trait作参数-impl trait

使用 trait 来接受多种不同类型的参数

// 对于 item 参数,指定 impl 关键字和 trait 名称,而不是具体的类型
// 该参数支持任何实现了指定 trait 的类型
pub fn notify(item: impl Summary) {
    // 在 notify 函数体中,可以调用任何来自 Summary trait 的方法
    // 可以传递任何 NewsArticle 或 Tweet 的实例来调用 notify
    println!("Breaking news! {}", item.summarize());
}

trait作参数-trait Bound

impl Trait 很方便,适用于短小的例子
trait bound 则适用于更复杂的场景

// trait bound 与泛型参数声明在一起,位于尖括号中的冒号后面
pub fn notify<T: Summary>(item: T) {
    println!("Breaking news! {}", item.summarize());
}

获取两个实现了 Summary 的参数

// 适用于 item1 和 item2 允许是不同类型的情况(只要它们都实现了 Summary)
pub fn notify(item1: impl Summary, item2: impl Summary) {

// 强制它们都是相同类型
// 泛型 T 被指定为 item1 和 item2 的参数限制
// 如此传递给参数 item1 和 item2 值的具体类型必须一致。
pub fn notify<T: Summary>(item1: T, item2: T) {

指定多个 trait bound

既要显示 item 的格式化形式,也要使用 summarize 方法

pub fn notify(item: impl Summary + Display) {
pub fn notify<T: Summary + Display>(item: T)

简化trait bound

每个泛型有其自己的 trait bound

fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {
fn some_function<T, U>(t: T, u: U) -> i32
    where T: Display + Clone,
          U: Clone + Debug
{

返回值是trait类型

和go的interface太像了,但又像阉割版的
调用方并不知情返回的类型,只适用于返回单一类型的情况

fn returns_summarizable() -> impl Summary {
    Tweet {
        username: String::from("horse_ebooks"),
        content: String::from("of course, as you probably already know, people"),
        reply: false,
        retweet: false,
    }
}

糟糕的例子

fn returns_summarizable(switch: bool) -> impl Summary {
    if switch {
        NewsArticle {
            headline: String::from("Penguins win the Stanley Cup Championship!"),
            location: String::from("Pittsburgh, PA, USA"),
            author: String::from("Iceburgh"),
            content: String::from("The Pittsburgh Penguins once again are the best
            hockey team in the NHL."),
        }
    } else {
        Tweet {
            username: String::from("horse_ebooks"),
            content: String::from("of course, as you probably already know, people"),
            reply: false,
            retweet: false,
        }
    }
}

最开始例子的修复

使用泛型参数 trait bound 来指定所需的行为
在 largest 函数体中想要使用大于运算符(>)比较两个 T 类型的值。这个运算符被定义为标准库中 trait std::cmp::PartialOrd 的一个默认方法。

修改函数签名

fn largest<T: PartialOrd>(list: &[T]) -> T {

// 报错如下
// cannot move out of type `[T]`, a non-copy slice
// i32 和 char 这样的类型是已知大小的并可以储存在栈上,所以他们实现了 Copy trait
// 将 largest 函数改成使用泛型后,现在 list 参数的类型就有可能是没有实现 Copy trait
// 意味着可能不能将 list[0] 的值移动到 largest 变量中

解决方法1

为了只对实现了 Copy 的类型调用这些代码
可以在 T 的 trait bounds 中增加 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);
}

解决方法2

不希望限制 largest 函数只能用于实现了 Copy trait 的类型
可以在 T 的 trait bounds 中指定 Clone 而不是 Copy
克隆 slice 的每一个值使得 largest 函数拥有其所有权
使用 clone 函数意味着对于类似 String 这样拥有堆上数据的类型
会潜在的分配更多堆上空间
而堆分配在涉及大量数据时可能会相当缓慢

解决方式3

返回在 slice 中 T 值的引用
如果将函数返回值从 T 改为 &T 并改变函数体使其能够返回一个引用
将不需要任何 Clone 或 Copy 的 trait bounds 而且也不会有任何的堆分配

trait Bound 有条件地实现方法

use std::fmt::Display;

struct Pair<T> {
    x: T,
    y: T,
}

impl<T> Pair<T> {
    fn new(x: T, y: T) -> Self {
        Self {
            x,
            y,
        }
    }
}

// 只有那些为 T 类型实现了 PartialOrd trait (来允许比较) 和 Display trait (来启用打印)的 Pair<T> 
// 才会实现 cmp_display
impl<T: Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("The largest member is x = {}", self.x);
        } else {
            println!("The largest member is y = {}", self.y);
        }
    }
}

对任何实现了特定 trait 的类型有条件地实现 trait

blanket implementations,广泛用于 Rust 标准库
标准库为任何实现了 Display trait 的类型实现了 ToString trait
对任何实现了 Display trait 的类型调用由 ToString 定义的 to_string 方法
// 某个行为依赖于另一个行为

impl<T: Display> ToString for T {
    // --snip--
}

使用场景

let s = 3.to_string();

总结

动态类型语言中如果尝试调用一个类型并没有实现方法,运行时报错
Rust 将这些错误移动到编译时,甚至在代码能运之前就强迫修复错误
无需编写运行时检查行为的代码,因为在编译时就已经检查过了
相比其他不愿放弃泛型灵活性的语言有更好的性能文章来源地址https://www.toymoban.com/news/detail-580304.html

到了这里,关于rust学习-泛型和trait的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Kotlin(十七) 泛型和委托

    泛型的基本用法 泛型主要有两种定义方式:一种是定义泛型类,另一种是定义泛型方法,使用的语法结构都是 T 。当然括号内的 T 并不是固定要求的,事实上你使用任何英文字母或单词都可以,但是通常情况下, T 是一种约定俗成的泛型写法。 定义一个泛型类,示例如下:

    2024年02月04日
    浏览(39)
  • Swift-31-泛型和类型操作

    Swift泛型(generics) 让我们写出的类型和函数可以使用对于我们或编译器都未知的类型。 很多内建类型(包括可空类型、数组和字典)都是用泛型实现的,比如数组和一些集合就是用泛型方式来实现的。 一种运行时进行类型检查的技术,效率高但是不安全。在swift中泛型可用于结构

    2024年04月28日
    浏览(35)
  • rust学习-trait std::cmp::PartialEq、Eq、PartialOrd、Ord

    对于不具有完全等价关系type,此trait允许部分相等(This trait allows for partial equality, for types that do not have a full equivalence relation)。 例如,在浮点数(floating point numbers)中 NaN != NaN,因此浮点类型实现 PartialEq 但不实现 Eq。 Formally speaking正式来说, 当Rhs==Self时,这个特质对应于

    2024年02月08日
    浏览(42)
  • Java中泛型和Object类型 初级进阶教程(一)

    在学习的过程中,常常看到某个类或者接口等中使用 ListT, TestT,其中T的作用是什么呢? 1 在类中使用泛型 2 使用多个泛型 3 在类中使用泛型 4 在方法中使用泛型 5 限制泛型类型 6 通配符 (Wildcard) 总结:泛型和Object类型之间的区别 类型安全: 泛型 T : 泛型提供了编译时类型

    2024年02月01日
    浏览(54)
  • 泛型、Trait 和生命周期(上)

    目录 1、提取函数来减少重复 2、在函数定义中使用泛型 3、结构体定义中的泛型  4、枚举定义中的泛型 5、方法定义中的泛型 6、泛型代码的性能 每一门编程语言都有高效处理重复概念的工具。在 Rust 中其工具之一就是  泛型 ( generics )。泛型是具体类型或其他属性的抽象

    2024年02月21日
    浏览(30)
  • go-easy-utils 2.0 正式发布,全面支持泛型和any

    这是一个基于 Go 语言开发的通用数据类型处理工具类,帮助开发者在业务代码实现中处理常见的数据类型和数据操作。可以让您专注于您的业务代码的实现,而免去处理基本数据类型转换和验证的功能。该工具库无侵入式的设计可以让您的业务代码更容易阅读和优雅。 安装

    2023年04月13日
    浏览(36)
  • 【Rust 基础篇】Rust Sized Trait:理解Sized Trait与动态大小类型

    Rust是一门以安全性和性能著称的系统级编程语言。在Rust中,类型大小的确定在编译期是非常重要的。然而,有些类型的大小在编译期是无法确定的,这就涉及到了Rust中的动态大小类型(DST)。为了保证在编译期可以确定类型的大小,Rust引入了Sized trait。本篇博客将深入探讨

    2024年02月14日
    浏览(37)
  • 【Rust 基础篇】Rust 特征(Traits)

    Rust 的特征(Traits)是一种强大的语言机制,它允许我们定义共享行为并实现代码的抽象。通过特征,我们可以编写更加灵活和可复用的代码。本篇博客将详细介绍 Rust 特征的定义、实现和使用方法,包括特征的基本语法、默认实现、泛型特征以及特征的实现和使用。 在 Ru

    2024年02月15日
    浏览(50)
  • Rust-trait

    Rust语言中的trait是非常重要的概念。 在Rust中,trait这一个概念承担了多种职责。在中文里,trait可以翻译为“特征”“特点”“特性”等。 trait中可以定义函数。用例子来说明,我们定义如下的trait: 上面这个trait包含了一个方法,这个方法只有一个参数,这个self参数是什么意

    2024年01月20日
    浏览(43)
  • 【Rust 基础篇】Rust Deref Trait 的使用

    在 Rust 中,Deref trait 是一种特殊的 trait,用于重载解引用操作符 * 。通过实现 Deref trait,我们可以定义类型的解引用行为,使其在使用 * 运算符时表现得像引用类型。 本篇博客将详细介绍 Rust 中如何实现和使用 Deref trait,以及它在代码中的应用场景。 Deref trait 的定义如下:

    2024年02月17日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包