Rust系列(三) 类型系统与trait

上一篇:Rust系列(二) 内存管理

本质:类型系统本质是一种工具,用于在编译时对数据做静态检查,或者在运行时对数据做动态检查的时候,以确保对数据进行某个转换/修改操作,符合开发者的期望。
强类型和弱类型:按定义后类型是否可以隐式转换,可以分为强类型和弱类型。Rust 不同类型间不能自动转换,所以是强类型语言,而 C / C++ / JavaScript 会自动转换,是弱类型语言。

一、多态的实现

● 动态类型系统:Duck typing
● 静态类型系统:参数多态特设多态子类型多态
简而言之,参数多态是指函数可以有多种不同的参数列表实现;特设多态一般指函数的重载;子类型多态是指,在运行时,子类型可以被当成父类型使用。在 Rust 中,参数多态通过泛型来支持、特设多态通过 trait 来支持、子类型多态可以用 trait object 来支持

二、类型系统的特点

2.1 不允许隐式转换类型

C/C++/Java 这些定义后数据可以隐式转换类型的弱类型语言,不是内存安全的,而 Rust 由于不允许隐式类型转换,所以不会出现由于隐式类型转换,导致读取不正确的数据,甚至内存访问越界的问题。

2.2 只允许访问被授权的内存

为了做到这么严格的类型安全,Rust 中除了 let / fn / static / const 这些定义性语句外,都是表达式,而一切表达式都有类型,所以可以说在 Rust 中,类型无处不在。

if exist {
    do_something();
}

如上面的这段代码所示,虽然没有显示的返回值,但编译器会返回一个unit(),unit是只有一个值的类型,它的值和类型都是 ()。

2.3 支持类型推导

如果你同时写过Javago你会发现Java的每一个类型都需要手动声明,而 go则可以直接使用:=由编译器自动推导出变量的类型。显然,每一个类型都需要手动声明,
似乎不太符合rust新一代编程语言的B格,所以rust也支持局部的自动类型推导,如下图所示:
rust auto type
以上不仅可以推导出num的数据类型,还可以通过下面mapinsert操作,推断出map的类型。
turbofish语法

use std::net::SocketAddr;

fn main() {
    let addr = "127.0.0.1:8080".parse::<SocketAddr>().unwrap();
    println!("addr: {:?}, port: {:?}", addr.ip(), addr.port());
}

如上所示,在泛型函数后使用::来强制使用类型 T=SocketAddr,这种写法被称为 turbofish

2.4 原生类型

具体可以参考官网链接:Primitive Types

Type中文翻译Description
never从不The ! type, also called “never”.
array数组A fixed-size array, denoted [T; N], for the element type, T, and the non-negative compile-time constant size, N.
bool布尔类型The boolean type.
char字符类型A character type.
f32单精度浮点数A 32-bit floating point type (specifically, the “binary32” type defined in IEEE 754-2008).
f64双精度浮点数A 64-bit floating point type (specifically, the “binary64” type defined in IEEE 754-2008).
fn函数指针Function pointers, like fn(usize) -> bool.
i88位有符号整数The 8-bit signed integer type.
i1616位有符号整数The 16-bit signed integer type.
i3232位有符号整数The 32-bit signed integer type.
i6464位有符号整数The 64-bit signed integer type.
i128128位有符号整数The 128-bit signed integer type.
isize指针大小有符号整数The pointer-sized signed integer type.
pointer指针Raw, unsafe pointers, *const T, and *mut T.
reference引用References, &T and &mut T.
slice切片A dynamically-sized view into a contiguous sequence, [T]. Contiguous here means that elements are laid out so that every element is the same distance from its neighbors.
str字符串切片String slices.
tuple元组A finite heterogeneous sequence, (T, U, …).
u88位无符号整数The 8-bit unsigned integer type.
u1616位无符号整数The 16-bit unsigned integer type.
u3232位无符号整数The 32-bit unsigned integer type.
u6464位无符号整数The 64-bit unsigned integer type.
u128128位无符号整数The 128-bit unsigned integer type.
unit空类型The () type, also called “unit”.
usize指针大小无符号整数The pointer-sized unsigned integer type.

2.5 泛型函数

对于泛型函数,Rust 会进行单态化(Monomorphization)处理,也就是在编译时,把所有用到的泛型函数的泛型参数展开,生成若干个函数。
单态化的好处是,泛型函数的调用是静态分派(static dispatch),在编译时就一一对应,既保有多态的灵活性,又没有任何效率的损失,和普通函数调用一样高效。
但单态化有很明显的坏处,就是编译速度很慢,一个泛型函数,编译器需要找到所有用到的不同类型,一个个编译,所以 Rust 编译代码的速度总被人吐槽,这和单态化脱不开干系(另一个重要因素是宏)。

三、如何使用trait来定义接口?

3.1 基本 trait

话不多说都在代码注释中了,可以和JavaInterface对比学习。需要注意的一个点是,rust的各种trait限定规则,总是会莫名奇妙的踩坑。

use regex::Regex;

pub trait Parse {
    type Error;
    // parse 方法是 trait 的静态方法,因为它的第一个参数和 self 无关,所以在调用时需要使用 T::parse(str)
    fn parse(s: &str) -> Result<Self, Self::Error>
        // 因为返回值包含 Parse trait 的关联类型 Error,
        // 所以需要显式地添加 Sized 限定来确保函数的返回值类型是固定大小的
        where Self: Sized;
}

impl Parse for u8 {
    type Error = String;
    fn parse(s: &str) -> Result<Self, Self::Error> {
        // r开头的字符串表示原始字符串字面量(raw string literal)
        // 在原始字符串字面量中,转义字符会被忽略,也就是说反斜杠 '\' 不会被解释为转义字符
        let re: Regex = Regex::new(r"^[0-9]+").unwrap();
        if let Some(captures) = re.captures(s) {
            captures.get(0)
                .map_or(Err("failed to captures".to_string()),
                        |s| s.as_str().parse()
                            .map_err(|_err| "failed to parse captured string".to_string()))
        } else {
            Err("failed to parse string".to_string())
        }
    }
}

#[test]
fn parse_should_work() {
    assert_eq!(u8::parse("123abc").unwrap(), 123);
    assert_eq!(u8::parse("1234abcd").unwrap_or(0), 0);
    assert_eq!(u8::parse("abcd").unwrap_or(0), 0);
}

fn main() {
    // 255
    println!("result: {}", u8::parse("255 hello world").unwrap_or(0));
}

3.2 泛型 trait

把上面为普通实现的trait改写为泛型trait即可:

use std::str::FromStr;

use regex::Regex;

pub trait Parse {
    type Error;
    fn parse(s: &str) -> Result<Self, Self::Error>
        where Self: Sized;
}

impl<T> Parse for T
    where T: FromStr {
    type Error = String;
    fn parse(s: &str) -> Result<Self, Self::Error> {
        let re: Regex = Regex::new(r"^[0-9]+(\.[0-9]+)?").unwrap();
        if let Some(captures) = re.captures(s) {
            captures.get(0)
                .map_or(Err("failed to captures".to_string()),
                        |s| s.as_str().parse()
                            .map_err(|_err| "failed to parse captured string".to_string()))
        } else {
            Err("failed to parse string".to_string())
        }
    }
}

#[test]
fn parse_should_work() {
    assert_eq!(u8::parse("123abc").unwrap(), 123);
    assert_eq!(u8::parse("1234abcd").unwrap_or(0), 0);
    assert_eq!(u8::parse("abcd").unwrap_or(0), 0);
    assert_eq!(u32::parse("123.45abcd").unwrap_or(0), 0);
    assert_eq!(u32::parse("123abcd").unwrap(), 123);
    assert_eq!(f32::parse("123.45abcd").unwrap(), 123.45);
}

fn main() {
    // 255
    println!("result: {}", u8::parse("255 hello world").unwrap_or(0));
    // 123.45
    println!("result: {}", f32::parse("123.45abcd").unwrap());
}

四、常用的trait

用好trait,会让代码结构更加清晰,阅读和使用都更加符合 Rust 生态的习惯。比如数据结构实现了 Debug trait,就可以用 {:?} 来打印;数据结构实现了 From<T> trait,就可以直接使用 into() 方法做数据转换。

4.1 Clone

● 官方文档:https://doc.rust-lang.org/std/clone/trait.Clone.html
Clone支持使用派生宏#[derive],前提条件是,结构体中的所有的子类型都实现了Clone trait,如下所示:

// `derive` implements Clone for Reading<T> when T is Clone.
#[derive(Clone)]
struct Reading<T> {
    frequency: T,
}
  • 实现Clone trait的方式,可以参考下例:
#[derive(Debug)]
struct Generate<T> {
    inner: fn() -> T,
}

impl<T> Copy for Generate<T> {}

impl<T> Clone for Generate<T> {
    fn clone(&self) -> Self {
        Self {
            inner: self.inner
        }
    }
}

impl<T> Generate<T> {
    fn new(arg: fn() -> T) -> Self {
        Self {
            inner: arg,
        }
    }
}

fn getFunctionTag() -> i8 {
    // https://docs.rs/rand/latest/rand/#
    let base = rand::random::<i8>();
    return base + 1;
}

fn main() {
    let old_general = Generate::new(getFunctionTag);
    let clone_general = old_general.clone();
    let copy_general = old_general.clone();
    // inner address:Generate { inner: 0x7ff798931170 } value:86
    println!("inner address:{:?} value:{}", old_general, (old_general.inner)());
    // inner address:Generate { inner: 0x7ff798931170 } value:-44
    println!("inner address:{:?} value:{}", clone_general, (clone_general.inner)());
    // inner address:Generate { inner: 0x7ff798931170 } value:-33
    println!("inner address:{:?} value:{}", copy_general, (copy_general.inner)());
}

4.2 Copy

// 约束必须实现Clone trait
pub trait Copy: Clone { }
  • 因为Copy约束必须实现Clone trait, 可以用作 trait bound 来进行类型安全检查。

4.3 Drop

  • 官方文档:https://doc.rust-lang.org/std/ops/trait.Drop.html
  • Copy traitDrop trait 是互斥的,两者不能共存,当你尝试为同一种数据类型实现 Copy,也实现 Drop,编译器就会报错。这其实很好理解:Copy 是按位做浅拷贝,那么它会默认拷贝的数据没有需要释放的资源;而 Drop 恰恰是为了释放额外的资源而生的。
  • 简单使用示例:
struct HasDrop;

impl Drop for HasDrop {
    fn drop(&mut self) {
        println!("Dropping HasDrop!");
    }
}

struct HasTwoDrops {
    one: HasDrop,
    two: HasDrop,
}

impl Drop for HasTwoDrops {
    fn drop(&mut self) {
        println!("Dropping HasTwoDrops!");
    }
}

fn main() {
    let _x = HasTwoDrops { one: HasDrop, two: HasDrop };
    println!("Running!");
    // Running!
    // Dropping HasTwoDrops!
    // Dropping HasDrop!
    // Dropping HasDrop!
}

下一篇:Rust系列(四) trait备忘录(持续更新)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值