写点什么

30 天拿下 Rust 之高级类型

作者:希望睿智
  • 2024-06-12
    安徽
  • 本文字数:2454 字

    阅读完需:约 8 分钟

30天拿下Rust之高级类型

💡 如果想阅读最新的文章,或者有技术问题需要交流和沟通,可搜索并关注微信公众号“希望睿智”。

概述

Rust 作为一门系统编程语言,以其独特的内存管理方式和强大的类型系统著称。其中,高级类型的应用,为 Rust 的开发者提供了丰富的编程工具和手段,使得开发者可以更加灵活和高效地进行编程。

Newtype 模式

Newtype 模式是一种轻量级的设计模式,用于封装和强化类型的行为,提供额外的类型安全和语义清晰度。本质上,Newtype 模式通过定义一个新的结构体,其唯一字段就是想要封装的基础类型,从而创建一个新的类型。Newtype 模式主要用于以下三个方面。

1、增加类型安全性:通过引入新的类型,避免无意间将一种类型误用为另一种相似但语义不同的类型。

2、提供清晰的语义:新类型的命名可以反映其特定用途或含义,提高代码的可读性和自文档化能力。

3、实现特定行为:通过为新类型添加方法或实现 Trait,使其具备与基础类型不同的功能。

在下面的示例代码中,CustomVec 封装了 Vec<T>,但只提供了 push 方法,没有提供删除元素的方法,从而限制了用户的操作。同时,CustomVec 也隐藏了 Vec<T>的内部实现细节,使得其接口更加清晰和易于理解。

struct CustomVec<T>(Vec<T>);
impl<T> CustomVec<T> { fn new() -> Self { CustomVec(vec![]) }
fn push(&mut self, item: T) { self.0.push(item) }}
fn main() { let mut arr = CustomVec::new(); arr.push(66); arr.push(99); arr.push(100);}
复制代码

为了进一步理解 Newtype 模式,我们再来看另一个例子。在下面的示例代码中,我们创建了一个名为 Kilometers 的新类型,封装了 u32 类型以表示千米数。通过实现 From<u32> Trait,我们可以方便地将米数转换为千米数。此外,我们还为 Kilometers 结构体添加了一个 to_meters 方法,用于将千米数转换回米数。这样的封装使得在计算平均速度时,我们无法错误地将米和千米混淆,提高了类型安全性。

struct Kilometers(u32);
impl From<u32> for Kilometers { fn from(meters: u32) -> Self { Self(meters / 1000) }}
impl Kilometers { fn to_meters(&self) -> u32 { self.0 * 1000 }}
fn calculate_average_speed(distance: Kilometers, time_in_hours: f32) -> f32 { distance.0 as f32 / time_in_hours}
fn main() { let distance: Kilometers = Kilometers::from(1000); let time = 5.0;
println!("distance is {} m", distance.to_meters()); let average_speed = calculate_average_speed(distance, time); println!("average speed is {:.2} km/h", average_speed);}
复制代码

可以看到,Newtype 模式是一种简单而强大的工具。通过创建新的类型封装基础类型,可以增强代码的类型安全性、清晰度和功能性,是编写健壮、易理解且符合语义的 Rust 代码的重要手段之一。

类型别名

Rust 中的类型别名是一种为现有类型创建新名称的机制,旨在提高代码的可读性和可维护性。通过类型别名,你可以给复杂或难以理解的类型赋予更具语义化的名称,使得代码意图更加清晰。在 Rust 中,使用 type 关键字来给予现有类型另一个名字。

在下面的示例代码中,Kilometers 是 i32 的同义词,它们的值在内存中是完全相同的。但通过使用别名,我们可以更好地表达数据的含义,提高代码的可读性。

fn main() {    type Kilometers = i32;        let x: i32 = 66;      let y: Kilometers = 99;    println!("{} {}", x, y);}
复制代码

上面的例子可能过于简单,体现不出类型别名的优点。但当遇到复杂的类型表达式和复杂的泛型类型时,类型别名可以使代码更易于阅读。在下面的示例代码中,我们为(f64, f64)类型创建了一个类型别名 Point,使得函数签名和变量声明更易理解。

type Point = (f64, f64);
fn calculate_distance(p1: Point, p2: Point) -> f64 { let dx = p1.0 - p2.0; let dy = p1.1 - p2.1; (dx.powi(2) + dy.powi(2)).sqrt()}
fn main() { let point1 = (3.0, 4.0); let point2 = (6.0, 8.0); let distance = calculate_distance(point1, point2); println!("{}", distance);}
复制代码

never type

在 Rust 中,never type 是一种特殊的类型,用!符号表示。never type 在 Rust 中起到了一个重要的作用,即在函数从不返回的时候充当返回值。比如:当使用 panic!函数时,程序会立即终止并返回一个 never type 的值。因此,never type 可以帮助开发者明确表示那些永远不会返回的函数或表达式。

需要注意的是,never type 不能用于创建实际的值,因为没有任何值可以表示一个永远不会返回的情况。然而,这并不意味着 never type 没有实际用途。相反,它提供了一种在类型系统中表示发散函数的方式,这些函数永远不会返回正常的结果。

在 Rust 中,使用 never type 可以使代码更加清晰和易于理解,特别是在处理错误和异常情况时。通过明确标记那些永远不会返回的函数或表达式,开发者可以更容易地识别和处理潜在的错误和异常情况,从而提高代码的可靠性和健壮性。

在下面的示例代码中,divide 函数接受两个 i32 类型的参数 a 和 b,并返回一个 Result<i32, !>类型的值。Result 类型是一个枚举,用于表示操作可能成功(Ok)或失败(Err)。然而,在这个特定的 divide 函数中,如果 b 为 0,我们触发 panic,这表示函数将不会返回正常的 Result 枚举值。因此,我们使用!作为 Err 的变体类型,表明在这种情况下函数不会返回任何错误值。在 main 函数中,当我们用非零的 b 值调用 divide 时,它会正常返回 Ok 的结果。然而,如果我们尝试用 0 作为 b 的值调用 divide,程序会触发 panic,并且不会返回任何值。

fn divide(a: i32, b: i32) -> Result<i32, !> {    if b == 0 {        panic!("division by zero!");      }  
Ok(a / b) } fn main() { let result = divide(100, 25); match result { Ok(val) => println!("result: {}", val), Err(e) => unreachable!("never be reached"), } let _ = divide(66, 0);}
复制代码


发布于: 刚刚阅读数: 5
用户头像

希望睿智

关注

一起学习,一起成长,一起进步! 2024-05-21 加入

中国科学技术大学毕业,在客户端、运营级平台、Web开发、嵌入式开发、深度学习、人工智能、音视频编解码、图像处理、流媒体等多个领域具备实战开发经验和技术积累,共发表发明专利十余项,软件著作权几十项。

评论

发布
暂无评论
30天拿下Rust之高级类型_rust_希望睿智_InfoQ写作社区