美文网首页
Rust for cpp devs - Generic Type

Rust for cpp devs - Generic Type

作者: 找不到工作 | 来源:发表于2021-05-01 22:26 被阅读0次

类似于 cpp,Rust 中也有泛型(generics),用于避免重复的代码。此外,还可以指定 traits 来限制泛型必须实现某个功能。

Generics

我们可以在函数、结构体、枚举类型中使用泛型。

函数中的泛型

我们定义了以下函数:

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

这个函数可以用于求各类型数组的最大值,避免了为每个类型实现同样的函数。

fn largest<T>(list: &[T]) -> &T {
    let mut largest = &list[0];
    for item in list {
        if item > largest {
            largest = item
        }
    }

    return largest;
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];
    println!("The largest number is {}", largest(&number_list));

    let char_list = vec!['y', 'm', 'a', 'q'];
    println!("The largest char is {}", largest(&char_list));
}

但是,由于不是所有类型都能使用 > 进行比较,编译时会报错:

error[E0369]: binary operation `>` cannot be applied to type `&T`
 --> src/main.rs:4:17
  |
4 |         if item > largest {
  |            ---- ^ ------- &T
  |            |
  |            &T
  |
help: consider restricting type parameter `T`
  |
1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
  |             ^^^^^^^^^^^^^^^^^^^^^^

对于这个错误,我们会在之后讲 trait 时修复。

结构体中的泛型

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

fn main() {
    let integer = Point { x: 5, y: 10 };
    let float = Point { x: 1.0, y: 4.0 };
}

枚举类型中的泛型

enum Result<T, E> {
    Ok(T),
    Err(E),
}

泛型对 Runtime 性能无影响

泛型并不影响程序在运行时的速度,因为 Rust 会在编译时为使用泛型的代码填充具体类型。

Option<T> 举例说明。如果我们定义了:

let integer = Some(5);
let float = Some(5.0);

那么在编译期间,编译器会读取所有使用泛型的代码,并扩展成具体类型。

在这,我们使用了 i32f64,因此代码会扩展为:

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);
}

因为运行时,泛型已经被扩展为普通的函数,因此没有运行时开销。

Trait

trait 定义了一些类型的共有的功能,可以利用 trait 指定泛型必须有某种行为。

trait 和某些语言中的“接口”类似。

定义 trait

我们可以使用如下方式定义 trait:

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

注意这里使用了 pub 表明这个 trait 对该 crate 外是可见的。此外,我们只需要提供函数签名,而不需要实现。

实现 trait

实现这个 Summary trait 的类型需要提供这个 summarize 函数的实现,使用 impl...for... 语法,例如:

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

不能为一个外部的类型实现外部的 trait

要求 trait 或者类型至少有一方是自己定义在 crate 里的。

例如,我们无法为 Vec<T> 实现 Display trait。因为 Vec<T> 类型和 Display trait 都是在标准库中定义。

这样的限制是为了保证“一致性”,即让一个 crate 无法影响其依赖包的行为。否则,两个 crates 可以为同一个类型实现同一个 trait,Rust 无法决定使用哪个实现。

trait 的默认实现

在定义 trait 时,我们也可以给出默认实现。默认实现的方法可以调用 trait 中的其他方法,即使没有默认实现:

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

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

这样在实现 trait 时,summarize_author 是必须实现的,而summarize 可以采用默认实现:

impl Summary for Tweet {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }
    // use default implementation for summarize()
}

用 trait 做参数类型

我们可以用 trait 而非实际类型来作为函数的参数。如:

fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

对于要实现多个 trait 的类型,我们可以用 + 连接:

fn notify<T: Summary + Display>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

利用 trait bounds 修复 largest 函数

我们只需要指明 T 是实现了 std::cmp::PartialOrd trait 的(T: PartialOrd):

fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];
    for item in list {
        if item > largest {
            largest = item
        }
    }

    return largest;
}

利用 trait bounds 为特定类型实现方法

当我们使用泛型类型时,通过 trait bound 可以为某些 trait 的类型实现方法。

如果某个方法是对所有类型的,可以写在这个 scope 里:

impl<T> Pair<T> {
}

如果仅对满足 DisplayPartialOrd 的类型生效,可以写在这个 scope 里:

impl<T: PartialOrd + Display> Pair<T> {
}

例如:

struct Pair<T> {
    first: T,
    second: T,
}

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

impl<T: PartialOrd + Display> Pair<T> {
    fn display_greater(&self) {
        let greater = if self.first > self.second {&self.first} else {&self.second};
        println!("The greater element is {}", greater);
    }
}

fn main() {
    let pair = Pair::new(3, 4);
    pair.display_greater();
}

达到的效果是,对所有类型的 Pair,都有 new 方法,但是仅对实现了 DisplayPartialOrd trait 类型的 Pair,有 display_greater 方法。

此外,我们还可以为任意类型实现 trait,语法是:

impl<T: Trait1> function for T {
}

这个在 Rust 标准库中非常常用。例如:

/// # Panics
///
/// In this implementation, the `to_string` method panics
/// if the `Display` implementation returns an error.
/// This indicates an incorrect `Display` implementation
/// since `fmt::Write for String` never returns an error itself.
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: fmt::Display + ?Sized> ToString for T {
    // A common guideline is to not inline generic functions. However,
    // removing `#[inline]` from this method causes non-negligible regressions.
    // See <https://github.com/rust-lang/rust/pull/74852>, the last attempt
    // to try to remove it.
    #[inline]
    default fn to_string(&self) -> String {
        use fmt::Write;
        let mut buf = String::new();
        buf.write_fmt(format_args!("{}", self))
            .expect("a Display implementation returned an error unexpectedly");
        buf
    }
}

相关文章

  • Rust for cpp devs - Generic Type

    类似于 cpp,Rust 中也有泛型(generics),用于避免重复的代码。此外,还可以指定 traits 来限...

  • Rust for cpp devs - 线程

    由于 Rust 特有的 ownership 和类型检查机制,许多并发问题都可以在编译期发现,这极大地降低了风险以及...

  • Rust for cpp devs - mutex

    除了 channel[https://www.jianshu.com/p/925d3534ac7f],我们也可以通...

  • Rust for cpp devs - Ownership

    编程语言的内存管理一般有两种: 带垃圾回收机制的,如 Java,Golang,会在运行时检查不再使用的内存并回收,...

  • Rust for cpp devs - channel

    与 golang 一样,Rust 也实现了 channel 用于线程间的通信。如同 golang 的口号一样: D...

  • Rust for cpp devs - closure

    类似于 cpp 中的 lambda 表达式,Rust 中也有 closure。他们与普通函数的区别在于可以捕获变量...

  • Rust for cpp devs - 迭代器

    迭代器(Iterator)可以允许对序列中的每一个元素执行某个操作。 Rust 的迭代器分为三种: iter() ...

  • Rust for cpp devs - minigrep 项目

    官方文档用 minigrep 项目来讲解如何组织一个 Rust 项目。 保持 main 函数简洁 这样做的好处是:...

  • Rust for cpp devs - 错误处理

    Rust 将软件中的错误分为两个类型:可恢复错误和不可恢复错误。 对于可恢复错误,例如文件找不到,可以报告给调用者...

  • Rust for cpp devs - 生命周期

    生命周期(lifetime)也是一类泛型。但它是用于指定引用保持有效的作用域。 Rust 中的每个引用都有相应的 ...

网友评论

      本文标题:Rust for cpp devs - Generic Type

      本文链接:https://www.haomeiwen.com/subject/iyzglltx.html