Rust中如何复用代码?

简介: Rust中如何复用代码?

Rust中如何复用代码?

我们在编写代码的时候需要考虑代码的复用性,通常情况下我们会使用提取函数和使用泛型来对代码进行复用

提取函数消除重复

重复代码

假如我们现在要比较出一个vector中的最大值,我们会这样写:

let l = vec![20, 44, 13, 22, 77, 8];
let mut largest = l[0];
for num in l {
    if largest < num {
        largest = num;
    }
}
println!("the largest number is {}", largest); //the largest number is 77

然后我们现在又要在另外一个Vector中挑出它的最大值:

let l = vec![20, 44, 13, 22, 77, 8];
let mut largest = l[0];
for num in l {
    if largest < num {
        largest = num;
    }
}
println!("the largest number is {}", largest_num); //the largest number is 77

let l = vec![200, 404, 103, 202, 77, 80];
let mut largest = l[0];
for num in l {
    if largest < num {
        largest = num;
    }
}
println!("the largest number is {}", largest); //the largest number is 404

我们发现有重复的代码(2-7和11-16),对于重复的代码:

  • 容易出错
  • 需求变更时需要在多处进行修改

为了消除重复,我们可以提取函数

fn largest_number(list: &[i32]) -> i32 {
    let mut largest_num = list[0];
    for &num in list {
        if largest_num < num {
            largest_num = num;
        }
    }
    largest_num
}
fn main() {
    let list = vec![20, 44, 13, 22, 77, 8];
    let largest = largest_number(&list);
    println!("the largest number is {}", largest); //the largest number is 77

    let list = vec![200, 404, 103, 202, 77, 80];
    let largest = largest_number(&list);
    println!("the largest number is {}", largest); //the largest number is 404
}

其中,传入的list类型为&[i32]实际上它是一个切片(我们必须传入引用,因为Rust不知道它的长度)。&num类型为i32,num类型为&i32。&num实际上进行了一个解构。我们如果不使用&,也可以在后面使用*进行解引用:

for num in list {
    if largest_num < *num {
        largest_num = *num;
    }
}

消除重复的步骤

  • 识别重复代码
  • 提取重复代码到函数体中,并在函数签名中指定函数的输入和返回值
  • 将重复的代码使用函数调用进行替代

泛型

泛型:提高代码复用能力,也就是说可以处理重复代码的问题

泛型是具体类型或其它属性的抽象代替:

  • 可以理解为:你使用泛型编写代码时不是最终的代码,而是一种模板,里面有一些 “占位符”
  • 编译器在编译时将“占位符”替换为具体的类型(这个过程叫单态化)

例如:fn largest<T>(list:&[T])->T{...}

这个T被称为类型参数:

  • 通常情况下很短,一般为一个字母,比如T(type的缩写)
  • 在Rust中使用CamelCase大驼峰命名法

在函数定义中的泛型

在上面的例子中的比较大小只能用于i32类型,我们使用泛型来将其能用作比较字母的大小:

fn the_largest<T>(list: &[T]) -> T {
    let mut largest = l[0];
    for &num in l {
        if largest < num {//这里会报错,因为并不是所有类型都支持比较,我们需要给泛型指定Trait,这里先不管
            largest = num;
        }
    }
    largest
}
fn main() {
    let arr = [1, 3, 5, 7, 9];
    let l = vec![20, 44, 13, 22, 77, 8];
    let largest = the_largest(&list);
    println!("the largest number is {}", largest); //the largest number is 77

    let l = vec!['a', 's', 'e', 'b'];
    let largest = the_largest(&list);
    println!("the largest number is {}", largest); //the largest number is 404
}

上面的函数会报错,因为T没有实现std::cmp::PartialOrd这一Trait(接口interface),暂时先不管这个。

Struct定义中的泛型

struct Point<T> {
    x: T,
    y: T,
}
fn main() {
    let integer = Point { x: 1, y: 2 }; //Point<i32>
    let float = Point { x: 1.0, y: 2.0 }; //Point<f64>
}

多个类型参数:

struct Point<T, U> {
    x: T,
    y: U,
}
fn main() {
    let float_int = Point { x: 1.0, y: 2 }; //Point<f64, i32>
}

如果太多类型参数,代码的可阅读性会变差,需要重组为多个更小的单元

Enum中使用泛型

可以让枚举的变体持有泛型数据类型,例如我们之前用到的Option\<T>,Result\<T,E>

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

方法定义中的泛型

为struct或enum实现方法的时候,可在定义中使用泛型

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

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

impl Point<i32> {
    fn x1(&self) -> &i32 {
        &self.x
    }
}

如果impl是根据Point\<T>来实现的(在类型T上实现方法),我们需要在impl后加\<T>。如果这个T是一个确切的类型,比如i32,我们就不需要加泛型T

另外,struct里的泛型类型参数可以和方法的泛型类型参数不同

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

impl<T, U> Point<T, U> {
    fn mix<V, W>(self, other: Point<V, W>) -> Point<T, W> {
        Point {
            x: self.x,
            y: other.y,
        }
    }
}

fn main() {
    let p1 = Point { x: 1, y: 2.3 };
    let p2 = Point { x: 'a', y: "demo" };
    let p3 = p1.mix(p2);
    println!("x:{},y:{}", p3.x, p3.y); //x:1,y:demo
}

在上面的例子中,我们Point的类型参数为T,U,impl实现的也是T,U。而我们的mix函数的泛型的类型参数为V,W。

上面mix方法的意思为接收一个<T,U>类型的Point,接收另一个Point,不过这里是占位符为V,W(类型参数)。然后返回的Point的x为self的x。而y为另一个Point的y。类型为:`<自身x的类型,other中y的类型>
`

泛型代码的性能

使用泛型的代码和使用具体类型的代码运行速度是一样的。

这是因为Rust在编译时会执行单态化(monomorphization)的过程:

  • 在编译时将泛型类型替换为具体类型的过程
fn main() {
    let integer = Some(5);
    let floater = Some(5.0);
}

上面的代码会被编译为这样(编译为具体的类型):

enum Option_i32 {
    Some(i32),
    None,
}
enum Option_f64 {
    Some(f64),
    None,
}
fn main() {
    let integer = Option_i32::Some(5);
    let floater = Option_f64::Some(5.0);
}
相关文章
|
5月前
|
存储 Rust 监控
Rust代码编写高性能屏幕监控软件的核心算法
本文介绍了使用Rust编写的高性能屏幕监控软件的实现方法。核心算法包括:1) 使用`image`和`winit`库捕获并转换屏幕图像;2) 对图像进行处理,检测特定对象或活动;3) 利用Rust的并发性并行处理多个帧以提高效率;4) 提取数据后,通过`reqwest`库自动提交到网站进行分析或存储。通过结合Rust的高性能和丰富的库,可构建满足各种需求的高效屏幕监控工具。
231 5
|
5月前
|
Rust 安全 编译器
Rust中的Raw Pointers与不安全代码:深入探索与最佳实践
本文旨在探讨Rust编程语言中Raw Pointers(原始指针)的使用场景以及如何安全地编写不安全代码。我们将深入了解Raw Pointers的定义、工作原理以及它们在Rust中的用途,同时还将讨论编写不安全代码的最佳实践和注意事项,以确保代码的稳定性和安全性。
|
1月前
|
Rust 索引
【Rust学习】08_使用结构体代码示例
为了了解我们何时可能想要使用结构体,让我们编写一个计算长方形面积的程序。我们将从使用单个变量开始,然后重构程序,直到我们改用结构体。
72 2
|
28天前
|
Rust 安全 程序员
30天拿下Rust之unsafe代码
30天拿下Rust之unsafe代码
20 0
|
2月前
|
Rust 开发者
揭秘Rust编程:模块与包的终极对决,谁将主宰代码组织的新秩序?
【8月更文挑战第31天】在软件工程中,模块化设计能显著提升代码的可读性、可维护性和可重用性。Rust 作为现代系统编程语言,其模块和包管理机制为开发者提供了强有力的工具来组织代码。本文通过对比模块和包的概念及使用场景,探讨了 Rust 中的最佳实践。
21 2
|
2月前
|
Rust 安全 JavaScript
Rust 和 WebAssembly 搞大事啦!代码在浏览器中运行,这波操作简直逆天!
【8月更文挑战第31天】《Rust 与 WebAssembly:将 Rust 代码运行在浏览器中》介绍了 Rust 和 WebAssembly 的强大结合。Rust 是一门安全高效的编程语言,而 WebAssembly 则是新兴的网页技术标准,两者结合使得 Rust 代码能在浏览器中运行,带来更高的性能和安全性。文章通过示例代码展示了如何将 Rust 函数编译为 WebAssembly 格式并在网页中调用,从而实现复杂高效的应用程序,同时确保了内存安全性和跨平台兼容性,为开发者提供了全新的可能性。
104 0
|
3月前
|
Rust 安全 开发者
Rust 问题之Rust-analyzer 提供了哪些功能来辅助编写 Rust 代码
Rust 问题之Rust-analyzer 提供了哪些功能来辅助编写 Rust 代码
|
3月前
|
存储 Rust JavaScript
Rust 问题之TypeScript 代码,变量 s 存储在栈内存中还是堆内存中如何解决
Rust 问题之TypeScript 代码,变量 s 存储在栈内存中还是堆内存中如何解决
|
4月前
|
监控 Rust 安全
Rust代码在公司电脑监控软件中的内存安全监控
使用 Rust 语言开发的内存安全监控软件在企业中日益重要,尤其对于高安全稳定性的系统。文中展示了如何用 Rust 监控内存使用:通过获取向量长度和内存大小来防止泄漏和溢出。此外,代码示例还演示了利用 reqwest 库自动将监控数据提交至公司网站进行实时分析,以保证系统的稳定和安全。
205 2
|
4月前
|
Rust 编译器
Rust代码组织:Package、Crate、Module
Rust代码组织:Package、Crate、Module