深入rust类型

类型转换

Rust 是强类型语言,同时也是强安全语言,它内置了一些基本类型之间的转换,如as 操作符: if a < (b as i32) {...}。通常我们是把范围较小的类型转换成较大的类型,来避免数据超范围。常见的一些转换形式:

fn main() {
   let a = 3.1 as i8;
   let b = 100_i8 as i32;
   let c = 'a' as u8; // 将字符'a'转换为整数,97

   println!("{},{},{}",a,b,c)
}

如果要处理转换错误,那么可以使用 TryInto特征,该特征通过​std::prelu模块提前引入到了当前作用域中,try_into 会尝试进行一次转换,并返回一个 Result,此时就可以对其进行相应的错误处理,例如,try_into 转换会捕获大类型向小类型转换时导致的溢出错误:

fn main() {
    let b: i16 = 1500;

    let b_: u8 = match b.try_into() {
        Ok(b1) => b1,
        Err(e) => {
            println!("{:?}", e.to_string());
            0
        }
    };
}

运行后输出如下 "out of range integral type conversion attempted"

asTryInto只能应用在数值类型上 ,在其他类型转换时需要强制转换,在某些情况下,类型是可以进行隐式强制转换的。但在匹配特征时,不会做任何强制转换(除了方法)。

自定义类型

newtype指的是使用元组结构体的方式将已有的类型包裹起来,例如:struct Meters(u32);这里Meters 就是一个 newtype;其主要作用有:1、为外部类型实现外部特征。比如前面说到的:如果要为类型 A 实现特征 T,那么 A 或者 T 必须至少有一个在当前的作用范围内。否则就得遵循孤儿规则。2、更好的可读性及类型异化。隐藏内部类型的细节

use std::ops::Add;
use std::fmt;

struct Meters(u32);
impl fmt::Display for Meters {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "目标地点距离你{}米", self.0)
    }
}

impl Add for Meters {
    type Output = Self;

    fn add(self, other: Meters) -> Self {
        Self(self.0 + other.0)
    }
}
fn main() {
    let d = calculate_distance(Meters(10), Meters(20));
    println!("{}", d);
}

fn calculate_distance(d1: Meters, d2: Meters) -> Meters {
    d1 + d2
}
//输出:
目标地点距离你30米

上面代码创建了一个 newtype Meters,为其实现 Display 和 Add 特征,接着对两个距离进行求和计算,最终打印出该距离

除了使用 newtype,我们还可以使用类型别名来创建新类型,但类型别名仅仅是别名,只是为了让可读性更好,并不是全新的类型,而且类型别名无法实现为外部类型实现外部特征等功能。然而类型别名可以让类型可读性更好,还能减少模版代码的使用。在标准库中,类型别名应用最广的就是简化 Result<T, E> 枚举,在使用 std::io 库时,它的所有错误类型都是 std::io::Error。而使用别名之后,std::io::Result<T> 即可替代冗长的 std::result::Result<T, std::io::Error> 类型。

type Result<T> = std::result::Result<T, std::io::Error>;

panic 的返回值是 !,代表它决不会返回任何值。

定长与不定长类型

从编译器何时能获知类型大小的角度来看,类型分为定长类型(sized)与不定长类型( unsized ),它的大小只有到了程序运行时才能动态获知,又被称之为 DST;切片和str都是一个典型的 DST 类型,str既不是 String 动态字符串,也不是 &str 字符串切片,它是 String 和 &str 的底层数据类型,属于是一个动态类型。而 &str 的引用有了底层堆数据的明确信息,它的引用存储在栈上,具有固定大小,因此是固定大小类型。所以,将动态数据固定化的方法就是使用引用指向这些动态数据,然后在引用中存储相关的内存位置、长度等信息。

let s1: str = "Hello there!";// error
let s3: &str = "on?"// ok

而对于泛型参数 T,编译器自动帮我们加上了 Sized 特征约束,表示泛型函数只能用于一切实现了 Sized 特征的类型,以保证T是固定大小的类型,几乎所有类型都实现了 Sized 特征。如果确实需要在泛型函数中使用动态数据类型,则需使用 ?Sized 特征,表明类型 T 既有可能是固定大小的类型,也可能是动态大小的类型。

fn generic<T: ?Sized>(t: &T) {

// --snip--

}

Rust 中常见的 DST 类型有: str[T]dyn Trait它们都无法单独被使用,必须要通过引用或者 Box 来间接使用 。

fn foobar_1(thing: &dyn MyThing) {}     // OK
fn foobar_2(thing: Box<dyn MyThing>) {} // OK
fn foobar_3(thing: MyThing) {}          // ERROR!

每一个特征都是一个可以通过名称来引用的动态大小类型。因此如果想把特征作为具体的类型来传递给函数,必须将其转换成一个特征对象:诸如 &dyn Trait 或者 Box<dyn Trait> (还有 Rc<dyn Trait>);使用 Box 可以将一个动态大小的特征变成一个具有固定大小的特征对象

let s1: Box<str> = "Hello there!".into();

在 Rust 中,枚举很容易转换成整数,例如:let x = MyEnum::C as i32;但整数却不能直接转换成枚举,常用的方法有:1、使用三方库num-traitsnum-derive。2、使用TryFrom特征。3、使用std::mem::transmute直接进行转换,例如 let z: MyEnum = unsafe { std::mem::transmute(y) };

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值