数据类型

类型转换

原生类型

Rust 不提供原生类型之间的隐式类型转换(coercion),但可以使用 as 关键字进行显 式类型转换(casting)。 当把任何类型转换为无符号类型 T 时,会不断加上或减去 (std::T::MAX + 1),直到值位于新类型 T 的范围内。

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

fn main() {
  let decimal = 65.4321_f32;

  // 错误!不提供隐式转换
  // let integer: u8 = decimal;
  // 改正 ^ 注释掉这一行

  // 可以显式转换
  let integer = decimal as u8;
  let character = integer as char; // A的ASCII码为65

  println!("Casting: {} -> {} -> {}", decimal, integer, character);

  // 当把任何类型转换为无符号类型 T 时,会不断加上或减去 (std::T::MAX + 1)
  // 直到值位于新类型 T 的范围内。

  let single_char = 'A';
  let unicode = '😇';
  println!("single char is {}", single_char as u8);
  println!("single unicode is {}", unicode as u8);
  println!("128519 mod 256 {}", 128519 % 256);

  // 1000 已经在 u16 的范围内
  println!("1000 as a u16 is: {}", 1000 as u16);

  // 1000 - 256 - 256 - 256 = 232
  // 事实上的处理方式是:从最低有效位(LSB,least significant bits)开始保留
  // 8 位,然后剩余位置,直到最高有效位(MSB,most significant bit)都被抛弃。
  // 译注:MSB 就是二进制的最高位,LSB 就是二进制的最低位,按日常书写习惯就是
  // 最左边一位和最右边一位。
  println!("1000 as a u8 is : {}", 1000 as u8);
  // -1 + 256 = 255
  println!("  -1 as a u8 is : {}", (-1i8) as u8);

  // 对正数,这就和取模一样。
  println!("1000 mod 256 is : {}", 1000 % 256);

  // 当转换到有符号类型时,(位操作的)结果就和 “先转换到对应的无符号类型,
  // 如果 MSB 是 1,则该值为负” 是一样的。

  // 当然如果数值已经在目标类型的范围内,就直接把它放进去。
  println!(" 128 as a i16 is: {}", 128 as i16);
  // 128 转成 u8 还是 128,但转到 i8 相当于给 128 取八位的二进制补码,其值是:
  println!(" 128 as a i8 is : {}", 128 as i8);

  // 重复之前的例子
  // 1000 as u8 -> 232
  println!("1000 as a u8 is : {}", 1000 as u8);
  // 232 的二进制补码是 -24
  println!(" 232 as a i8 is : {}", 232 as i8);
}

非原生类型

Rust 使用 trait 解决类型之间的转换问题。最一般的转换会用到 From 和 into 两个 trait。不过,即便常见的情况也可能会用到特别的 trait,尤其是 从 String 转换到别的类型,以及把别的类型转换到 String 时。

From 和 Into

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

Into trait 就是把From trait 倒过来而已。也就是说,如果你为你的类型实现了From,那么同时你也就免费获得了Into

use std::convert::From;

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

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

// 针对不同类型实现trait
impl From<char> for Number {
  fn from(item: char) -> Self {
    Number { value: item as i32 }
  }
}

fn main() {
  let int = 5;
  // 试试删除类型说明
  let num: Number = int.into();
  let num = Number::from(int);
  let num = Number::from('A');
  println!("My number is {:?}", num);
}

TryFrom 和 TryInto

类似于FromIntoTryFromTryInto是 类型转换的通用 trait。不同于 From/Into 的是,TryFromTryInto 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,并且还可以用来打印类型,就像 print! 一节中讨论的那样。

use std::string::ToString;
use std::fmt::{Display, Formatter, Result};

struct Circle {
    radius: i32
}

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

// Display trait 和 ToString trait 实现其中一个即可
impl Display for Circle {
  fn fmt(&self, f: &mut Formatter) -> Result {
    write!(f, "{}", self.radius)
  }
}

fn main() {
    let circle = Circle { radius: 6 };
    println!("{}", circle.to_string());
}

`FromStr'

我们经常需要把字符串转成数字。完成这项工作的标准手段是用 parse 函数。我们得 提供要转换到的类型,这可以通过不使用类型推断,或者用 “涡轮鱼” 语法(turbo fish,<>)实现。

只要对目标类型实现了 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};
}

类型别名

可以用 type 语句给已有的类型取个新的名字。类型的名字必须遵循大驼峰命名。也可用注解忽略#[allow(non_camel_case_types)].