rust学习笔记之基础:类型系统和类型转换

类型系统

原生类型的显式类型转换

Rust 不提供原生类型之间的隐式类型转换,但可以使用 as 关键字进行显式类型转换(casting)。

// 不显示类型转换产生的溢出警告。
#![allow(overflowing_literals)]

fn main() {
    let decimal = 65.4321_f32;

    // 错误!不提供隐式转换
    // let integer: u8 = decimal;

    // 可以显式转换
    let integer = decimal as u8;
    let character = integer as char;
    println!("Casting: {} -> {} -> {}", decimal, integer, character); // Casting: 65.4321 -> 65 -> A

    // 当把任何类型转换为无符号类型 T 时,会不断加上或减去 (std::T::MAX + 1) 直到值位于新类型 T 的范围内。
    // 1000 已经在 u16 的范围内
    println!("1000 as a u16 is: {}", 1000 as u16); // 1000 as a u16 is: 1000
    // 1000 - 256 - 256 - 256 = 232  事实上的处理方式是:从最低有效位开始保留 8 位,然后剩余位置都被抛弃。
    println!("1000 as a u8 is : {}", 1000 as u8); // 1000 as a u8 is : 232
    // -1 + 256 = 255
    println!("  -1 as a u8 is : {}", (-1i8) as u8); // -1 as a u8 is : 255
    // 对正数,这就和取模一样。
    println!("1000 mod 256 is : {}", 1000 % 256); // 1000 mod 256 is : 232

    // 当转换到有符号类型时,结果就和 “先转换到对应的无符号类型,如果最高有效位是 1,则该值为负” 是一样的。
    // 当然如果数值已经在目标类型的范围内,就直接把它放进去。
    println!(" 128 as a i16 is: {}", 128 as i16); // 128 as a i16 is: 128
    // 128 转成 u8 还是 128,但转到 i8 相当于给 128 取八位的二进制补码,其值是:
    println!(" 128 as a i8 is : {}", 128 as i8); // 128 as a i8 is : -128
    // 232 的二进制补码是 -24
    println!(" 232 as a i8 is : {}", 232 as i8); // 232 as a i8 is : -24
}

字面量

对数值字面量,只要把类型作为后缀加上去,就完成了类型说明。比如指定字面量 42 的 类型是 i32,只需要写 42i32。无后缀的数值字面量,其类型取决于怎样使用它们。如果没有限制,编译器会对整数使用 i32,对浮点数使用 f64。

fn main() {
    // 带后缀的字面量,其类型在初始化时已经知道了。
    let x = 1u8;
    let y = 2u32;
    let z = 3f32;

    // 无后缀的字面量,其类型取决于如何使用它们。
    let i = 1;
    let f = 1.0;

    // `size_of_val` 返回一个变量所占的字节数
    println!("size of `x` in bytes: {}", std::mem::size_of_val(&x)); // size of `x` in bytes: 1
    println!("size of `y` in bytes: {}", std::mem::size_of_val(&y)); // size of `y` in bytes: 4
    println!("size of `z` in bytes: {}", std::mem::size_of_val(&z)); // size of `z` in bytes: 4
    println!("size of `i` in bytes: {}", std::mem::size_of_val(&i)); // size of `i` in bytes: 4
    println!("size of `f` in bytes: {}", std::mem::size_of_val(&f)); // size of `f` in bytes: 8
}

类型推断

Rust 的类型推断引擎是很聪明的,它不只是在初始化时看看右值的类型而已,它还会考察变量之后会怎样使用,借此推断类型。这是一个类型推导的进阶例子:

fn main() {
    // 因为有类型说明,编译器知道 `elem` 的类型是 u8。
    let elem = 5u8;

    // 创建一个空向量,现在编译器还不知道 `vec` 的具体类型,只知道它是某种东西构成的向量(`Vec<_>`)
    let mut vec = Vec::new();
    // 在向量中插入 `elem`。
    vec.push(elem); // 现在编译器知道 `vec` 是 u8 的向量了(`Vec<u8>`)。
    println!("{:?}", vec);
}

别名

可以用 type 语句给已有的类型取个新的名字。类型的名字必须遵循驼峰命名法(像是 CamelCase 这样),否则编译器将给出警告。

type NanoSecond = u64;
type Inch = u64;

fn main() {
    let nanoseconds: NanoSecond = 5 as NanoSecond;
    let inches: Inch = 2 as Inch;

    println!("{} nanoseconds + {} inches = {} unit?", nanoseconds, inches, nanoseconds + inches);
}

别名的主要用途是避免写出冗长的模板化代码。如 IoResult<T>Result<T, IoError> 类型的别名。

类型转换

Rust 使用 trait 解决类型之间的转换问题。最常用的转换会用到 From 和 into 两个 trait。

From 和 Into

From 和 Into 两个 trait 是内部相关联的,实际上这是它们实现的一部分。如果我们能够从类型 B 得到类型 A,那么很容易相信我们也能把类型 B 转换为类型 A。

From

From trait 允许一种类型定义 “怎么根据另一种类型生成自己”,因此它提供了一种类型转换的简单机制。在标准库中有无数 From 的实现,规定原生类型及其他常见类型的转换功能。

比如,可以很容易地把 str 转换成 String:

fn main() {
    let my_str = "hello";
    let my_string = String::from(my_str);
}

也可以为我们自己的类型定义转换机制:

use std::convert::From;

#[derive(Debug)]
struct Number {
    value: i32,
}

impl From<i32> for Number {
    fn from(item: i32) -> Self {
        Number { value: item }
    }
}

fn main() {
    let num = Number::from(30);
    println!("My number is {:?}", num); // My number is Number { value: 30 }
}
Into

Into trait 就是把 From trait 倒过来而已。也就是说,如果你为你的类型实现了 From,那么同时你也就免费获得了 Into。使用 Into trait 通常要求指明要转换到的类型,因为编译器大多数时候不能推断它。

use std::convert::From;

#[derive(Debug)]
struct Number {
    value: i32,
}

impl From<i32> for Number {
    fn from(item: i32) -> Self {
        Number { value: item }
    }
}

fn main() {
    let int = 5;
    // 需要指明类型说明
    let num: Number = int.into();
    println!("My number is {:?}", num); // My number is Number { value: 5 }
}

TryFrom and TryInto

类似于 From 和 Into,TryFrom 和 TryInto 是类型转换的通用 trait。不同于 From/Into 的是,TryFrom 和 TryInto trait 用于易出错的转换,其返回值是 Result 型。

use std::convert::TryFrom;
use std::convert::TryInto;

#[derive(Debug, PartialEq)]
struct EvenNumber(i32);

impl TryFrom<i32> for EvenNumber {
    type Error = ();

    fn try_from(value: i32) -> Result<Self, Self::Error> {
        if value % 2 == 0 {
            Ok(EvenNumber(value))
        } else {
            Err(())
        }
    }
}

fn main() {
    // TryFrom
    assert_eq!(EvenNumber::try_from(8), Ok(EvenNumber(8)));
    assert_eq!(EvenNumber::try_from(5), Err(()));

    // TryInto
    let result: Result<EvenNumber, ()> = 8i32.try_into();
    assert_eq!(result, Ok(EvenNumber(8)));
    let result: Result<EvenNumber, ()> = 5i32.try_into();
    assert_eq!(result, Err(()));
}

ToString 和 FromStr

ToString

要把任何类型转换成 String,只需要实现那个类型的 ToString trait。然而不要直接这么做,实现 fmt::Display trait,它会自动提供 ToString,并且还可以用来打印类型。

use std::string::ToString;

struct Circle {
    radius: i32
}

impl ToString for Circle {
    fn to_string(&self) -> String {
        format!("Circle of radius {:?}", self.radius)
    }
}

fn main() {
    let circle = Circle { radius: 6 };
    println!("{}", circle.to_string()); // Circle of radius 6
}
解析字符串

我们经常需要把字符串转成数字。完成这项工作的标准手段是用 parse 函数。我们得提供要转换到的类型,这可以通过不使用类型推断。

只要对目标类型实现了 FromStr trait,就可以用 parse 把字符串转换成目标类型。标准库中已经给无数种类型实现了 FromStr。如果要转换到用户定义类型,只要手动实现 FromStr 就行。

fn main() {
    let parsed: i32 = "5".parse().unwrap();
    let turbo_parsed = "10".parse::<i32>().unwrap();

    let sum = parsed + turbo_parsed;
    println!{"Sum: {:?}", sum}; // Sum: 15
}
posted @ 2025-07-29 15:59  carol2014  阅读(9)  评论(0)    收藏  举报