Skip to content

内置类型

Rust 的内置类型(也叫原始类型 primitive type)是语言自带的基本类型, 不需要 use 导入, 直接就能用, 它们是所有其他类型的基础

标量类型

标量类型只代表一个值, 分为四类: 整数, 浮点数, 布尔, 字符

有符号整数无符号整数大小
i8u81 字节
i16u162 字节
i32u324 字节
i64u648 字节
i128u12816 字节
isizeusize跟系统位数有关(64 位系统 8 字节)
类型说明
f32单精度浮点数, 4 字节
f64双精度浮点数, 8 字节(默认浮点类型)
bool布尔值, true / false, 1 字节
char单个 Unicode 字符, 4 字节(注意不是 1 字节)

为什么 char 占 4 个字节?

因为 char 存储的是 Unicode 标量值(Unicode Scalar Value), 要能表示全世界的所有字符(包含中文/emoji), 4 字节才够用, 跟 C 语言的 char(1 字节)完全不同

复合类型

复合类型由多个值组合而成

类型说明
tuple元组, 可以放不同类型的值: (i32, bool, &str), 大小 = 各字段之和
array数组, 只能放相同类型的值, 长度固定: [i32; 3]
slice切片, 对连续内存的一块"视图", 没有所有权: &[i32]
str字符串切片, 一般以 &str 形式出现

其他内置类型

类型说明
fn函数指针, 可以像值一样传递函数
unit 单元类型 ()空元组, 表示"什么都没有", 占 0 字节, 函数无返回值时返回的就是它
裸指针*const T / *mut T, 不安全代码中使用

类型的大小

可以用 std::mem::size_of 查看每种类型占用的字节数

rust
fn main() {
    use std::mem::size_of;

    // 标量类型 (64 位系统)
    println!("i8:   {} 字节", size_of::<i8>());    // 1
    println!("i32:  {} 字节", size_of::<i32>());   // 4
    println!("i64:  {} 字节", size_of::<i64>());   // 8
    println!("f64:  {} 字节", size_of::<f64>());   // 8
    println!("bool: {} 字节", size_of::<bool>());  // 1
    println!("char: {} 字节", size_of::<char>());  // 4

    // 复合类型
    println!("(i32, bool): {} 字节", size_of::<(i32, bool)>()); // 8
    println!("[i32; 4]:    {} 字节", size_of::<[i32; 4]>());   // 16

    // 单元类型: 不占任何空间
    println!("():          {} 字节", size_of::<()>());          // 0
}

类型别名

type 关键字可以给已有类型起一个"外号", 方便书写和阅读

rust
// 语法: type 别名 = 原类型;
type Age = u8;              // 年龄用 u8 就够
type UserId = u64;          // 用户 id
type FnType = fn(i32, i32) -> i32; // 函数指针类型

fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn main() {
    // 别名可以直接当原类型使用
    let age: Age = 18;
    let id: UserId = 1001;
    println!("age = {age}, id = {id}"); // age = 18, id = 1001

    // 别名简化了函数指针类型的写法
    let f: FnType = add;
    println!("{}", f(1, 2)); // 3
}

别名不是新类型

别名只是原类型的"代号", 它们完全等价, 可以互相赋值; 如果想要一个全新的类型, 需要用"新类型模式"(元组结构体)

rust
type UserId = u64;

fn main() {
    let a: UserId = 1;
    let b: u64 = 2;

    // 别名和原类型完全等价, 直接互相赋值没问题
    let c: u64 = a;
    let d: UserId = b;
    println!("{c} {d}"); // 1 2
}
rust
// 元组结构体: 定义了一个全新的类型
struct UserId(u64);

fn main() {
    let a = UserId(1);
    let b: u64 = 2;

    // 编译报错: 新类型和原类型不能直接互相赋值
    // let c: u64 = a;
    // let d = UserId(b);

    // 必须手动取出内部的值
    let c: u64 = a.0;
    println!("{c}"); // 1
}

使用场景

rust
use std::collections::HashMap;

// 1. 简化复杂的泛型类型(重复写太长了)
type UserMap = HashMap<String, u64>;

// 2. 简化闭包/回调类型
type Callback = Box<dyn Fn(i32) -> i32>;

fn main() {
    // 使用别名
    let mut users: UserMap = HashMap::new();
    users.insert("tom".to_string(), 18);

    let cb: Callback = Box::new(|x| x * 2);
    println!("{}", cb(10)); // 20
}

注意点

  • 别名是"零成本"的, 编译后跟原类型一模一样, 不会带来任何运行时开销
  • 但别名没有类型安全: AgeUserId 如果都是 u8, 互相传参编译器不会报错
  • 需要类型安全(比如两个 id 不能混用), 请用新类型模式(元组结构体)

类型转换

Rust 的类型转换分为两大类: 隐式转换(编译器自动做) 和 显式转换(手动写)

转换方式类型特点典型场景
as 关键字显式只支持基本类型(数值/bool/char/指针)数值窄化/拓宽
From/Into显式所有权转换, 实现 From 自动获得 Into自定义类型转换
TryFrom/TryInto显式可能失败的转换, 返回 Result数值窄化(可能溢出)
AsRef/AsMut显式引用之间的转换, 零拷贝, 只换"视角"函数参数接收多种类型
FromStr/parse显式字符串 -> 其他类型, 可能失败字符串解析
Display/ToString显式其他类型 -> 字符串类型转字符串
Deref 强制转换隐式编译器自动解引用, 零成本&String -> &str

隐式转换

Rust 的隐式转换很少(这也是它安全的原因之一), 常见的只有这几种:

  1. 解引用强制转换: &String -> &str, &Vec<T> -> &[T](详细见标准库 trait)
  2. 可变性提升: &mut T 自动变成 &T
  3. 借用自动发生: 函数参数是 &T 时, 传 T 会自动加 &
  4. 数字字面量默认类型: 整数默认 i32, 浮点数默认 f64
rust
fn main() {
    // 1. 数字字面量的默认类型
    let x = 10;   // i32
    let y = 3.14; // f64

    // 2. 可变性提升: &mut T 自动变成 &T
    let mut s = String::from("hello");
    fn takes_str(s: &str) {
        println!("{s}");
    }
    // 传 &mut String 也能编译(先降级为 &String, 再解引用为 &str)
    takes_str(&mut s); // hello

    // 3. 解引用强制转换: &String -> &str
    takes_str(&s); // 传 &String 也能编译

    // 4. 解引用强制转换: &Vec<i32> -> &[i32]
    let v = vec![1, 2, 3];
    fn takes_slice(s: &[i32]) {
        println!("{s:?}");
    }
    takes_slice(&v); // 传 &Vec<i32> 也能编译
}

注意点

Rust 不会做以下隐式转换(其他语言会, 容易踩坑):

  • 整数不会自动变浮点数: let f: f64 = 10; 编译报错
  • 小整数不会自动变大整数: let b: u32 = 10u8; 编译报错
  • &str 不会自动变成 String(需要显式 to_string())

as 显式转换

as 关键字是最直接的类型转换, 只支持基本类型, 语法: 表达式 as 类型

转换示例说明
数值拓宽10u8 as u32小类型 -> 大类型, 值不变
数值窄化300u32 as u8大类型 -> 小类型, 溢出部分被截断
有符号 <-> 无符号-1i32 as u32按二进制位重新解释, 值会变
float -> 整数3.99f64 as i32直接截断小数部分
整数 -> float1i32 as f64可能存在精度损失
bool -> 整数true as u8true = 1, false = 0
char -> 整数'A' as u32得到字符的 Unicode 码点
引用 -> 裸指针&x as *const i32拿到内存地址(不安全代码用)
rust
fn main() {
    // 拓宽: 小类型 -> 大类型, 值不变
    let a: u8 = 255;
    let b = a as u32;
    println!("{b}"); // 255

    // 窄化: 大类型 -> 小类型, 溢出部分被截断
    let c: u32 = 300;
    let d = c as u8;
    println!("{d}"); // 44 (300 超过 u8 范围, 取余 256)

    // bool -> 整数
    println!("{}", true as u8);  // 1
    println!("{}", false as u8); // 0

    // float -> 整数: 小数部分直接截断(不是四舍五入)
    let pi = 3.99f64 as i32;
    println!("{pi}"); // 3

    // 有符号 -> 无符号: 按二进制位重新解释
    let n: i32 = -1;
    let m = n as u32;
    println!("{m}"); // 4294967295

    // char -> 整数: 得到 Unicode 码点
    let code = 'A' as u32;
    println!("{code}"); // 65
}

as 转换的注意点

rust
fn main() {
    // 编译报错: non-primitive cast: `&str` as `i32`
    // let n = "123" as i32;

    // 字符串 -> 数字必须用 parse()
    let n: i32 = "123".parse().unwrap();
    println!("{n}"); // 123
}
rust
fn main() {
    // 300 的二进制是 1 0010 1100 (9 位)
    // 截断成 u8(8 位): 0010 1100 = 44
    let big: u32 = 300;
    let small = big as u8;
    println!("{small}"); // 44
}
rust
fn main() {
    // u8 的 200 二进制是 1100 1000
    // 按 i8 解释(补码): 最高位是符号位, 结果是负数
    let u: u8 = 200;
    let i = u as i8;
    println!("{i}"); // -56
}

结论: 在数值之间转换用 as 没问题, 但要注意窄化会丢数据; 如果担心溢出, 应该用下面的 TryFrom/TryInto

标准库 trait

std::convert 模块提供了类型转换的核心 trait, 自定义类型的转换应该实现这些 trait, 而不是依赖 as

From 和 Into

  • From<T> for U: 定义了从 T 转换到 U 的规则, 调用方式是 U::from(t)
  • Into<U> for T: 是 From 的反向, 调用方式是 t.into()

只要实现了 From, 就会自动获得 Into, 所以一般只需要实现 From 一个就行

rust
fn main() {
    // 标准库已经实现好的转换

    // &str -> String
    let s1 = String::from("hello");
    let s2: String = "hello".into();
    println!("{s1} {s2}"); // hello hello

    // u8 -> u32
    let n1 = u32::from(10u8);
    let n2: u32 = 10u8.into();
    println!("{n1} {n2}"); // 10 10
}

自定义类型实现 From:

rust
struct Celsius(f64);     // 摄氏度
struct Fahrenheit(f64);  // 华氏度

// 实现 From: 华氏度 -> 摄氏度
// 公式: (F - 32) * 5 / 9
impl From<Fahrenheit> for Celsius {
    fn from(f: Fahrenheit) -> Self {
        Celsius((f.0 - 32.0) * 5.0 / 9.0)
    }
}

fn main() {
    // 方式一: 用 From 的关联函数
    let c = Celsius::from(Fahrenheit(212.0));
    println!("{}", c.0); // 100

    // 方式二: 用 .into() (实现 From 后自动获得)
    let c: Celsius = Fahrenheit(212.0).into();
    println!("{}", c.0); // 100

    // 常见用法: 函数参数自动转换
    fn show_temp(t: Celsius) {
        println!("{} 摄氏度", t.0);
    }
    show_temp(Fahrenheit(32.0).into()); // 0 摄氏度
}

转换的方向

impl From<A> for B 定义的是 A -> B 的转换:

  • B::from(a) 得到 B(目标类型 B 在前面)
  • a.into() 得到 B(需要标注目标类型, 否则编译器不知道转成什么)

TryFrom 和 TryInto

TryFrom 是"可能失败"的 From, 转换结果用 Result 返回: 成功是 Ok, 失败是 Err

rust
fn main() {
    // u32 -> u8 可能溢出, 标准库只提供了 TryFrom (没有 From)
    let big = 300u32;

    // 使用 try_into(), 返回 Result
    let small: Result<u8, _> = big.try_into();
    match small {
        Ok(v) => println!("转换成功: {v}"),
        Err(e) => println!("转换失败: {e}"),
        // 转换失败: out of range integral type conversion attempted
    }

    // 范围内的值可以成功
    let ok: Result<u8, _> = 100u32.try_into();
    println!("{ok:?}"); // Ok(100)
}

自定义类型实现 TryFrom:

rust
use std::convert::TryFrom;

// 校验年龄: 只接受 0-150 岁
// derive Debug 只是为了后面能用 {:?} 打印
#[derive(Debug)]
struct Age(u8);

impl TryFrom<i32> for Age {
    // 必须指定错误类型
    type Error = String;

    fn try_from(value: i32) -> Result<Self, Self::Error> {
        if value < 0 || value > 150 {
            Err(format!("年龄不合法: {value}"))
        } else {
            Ok(Age(value as u8))
        }
    }
}

fn main() {
    // 合法范围
    let age = Age::try_from(20);
    println!("{:?}", age.is_ok()); // true

    // 超出范围
    let age: Result<Age, _> = 200i32.try_into();
    println!("{age:?}"); // Err("年龄不合法: 200")
}

AsRef 和 AsMut

AsRef<T> / AsMut<T> 用于引用之间的转换, 转换前后不产生新数据, 只是换一种"视角"看同一块内存

rust
fn main() {
    let s = String::from("hello");

    // &String -> &str, 零成本
    let s1: &str = s.as_ref();
    println!("{s1}"); // hello

    // String 也实现了 AsRef<[u8]>, 可以当字节数组看
    let bytes: &[u8] = s.as_ref();
    println!("{bytes:?}"); // [104, 101, 108, 108, 111]
}

最常用的场景: 函数参数用泛型 T: AsRef<str>, 就能同时接收 &str, String, &String, Box<str> 等多种类型

rust
fn print_len<T: AsRef<str>>(text: T) {
    // as_ref 把各种类型统一转成 &str
    let s: &str = text.as_ref();
    println!("长度: {}", s.len());
}

fn main() {
    print_len("hello");                // 传入 &str
    print_len(String::from("hello"));  // 传入 String
    print_len(&String::from("hello")); // 传入 &String
}

FromStr 和 ToString

  • FromStr: 从字符串解析出其他类型, 字符串的 parse() 方法依赖它
  • ToString: 把类型转成字符串, 内部依赖 Display
rust
fn main() {
    // parse(): 字符串 -> 其他类型 (返回 Result)
    let n: i32 = "42".parse().unwrap();    // 字符串 -> 数字
    let b: bool = "true".parse().unwrap(); // 字符串 -> bool
    let f: f64 = "3.14".parse().unwrap();  // 字符串 -> float

    // 解析失败时返回 Err
    let result = "abc".parse::<i32>();
    println!("{result:?}"); // Err(ParseIntError { kind: InvalidDigit })

    // to_string(): 其他类型 -> 字符串 (要求实现 Display)
    let s = 42.to_string();    // 数字 -> 字符串
    let s2 = true.to_string(); // bool -> 字符串
    println!("{s} {s2}");      // 42 true
}

自定义类型实现 FromStr, 就能直接用 parse() 解析:

rust
use std::str::FromStr;

// derive Debug 只是为了后面能用 {:?} 打印
#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

// 约定字符串格式: "x,y", 比如 "1,2"
impl FromStr for Point {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let parts: Vec<&str> = s.split(',').collect();
        if parts.len() != 2 {
            return Err(format!("格式错误, 应该是 x,y: {s}"));
        }

        let x = parts[0].parse().map_err(|_| "x 不是数字".to_string())?;
        let y = parts[1].parse().map_err(|_| "y 不是数字".to_string())?;
        Ok(Point { x, y })
    }
}

fn main() {
    // 实现了 FromStr 后, 就可以用 parse() 了
    let p: Point = "1,2".parse().unwrap();
    println!("p = ({}, {})", p.x, p.y); // p = (1, 2)

    // 格式错误时返回 Err
    let bad: Result<Point, _> = "abc".parse();
    println!("{bad:?}"); // Err("格式错误, 应该是 x,y: abc")
}

Deref 强制转换

解引用强制转换(deref coercion)是前面提到的隐式转换, 当编译器需要 &str 却发现传过来的是 &String 时, 会自动调用 StringDeref 实现, 拿到内部的 &str

类型Deref 到自动转换示例
Stringstr&String -> &str
Vec<T>[T]&Vec<T> -> &[T]
Box<T>T&Box<T> -> &T
&TT&&T -> &T
rust
fn main() {
    let s = String::from("hello");
    let v = vec![1, 2, 3];

    fn takes_str(s: &str) -> usize {
        s.len()
    }

    fn takes_slice(s: &[i32]) -> usize {
        s.len()
    }

    // 不需要手动写 &s[..] 或 &v[..]
    // 编译器自动调用 Deref 完成转换
    println!("{}", takes_str(&s));   // 5
    println!("{}", takes_slice(&v)); // 3

    // 方法调用时也会自动解引用
    // s.trim() 实际调用的是 str::trim()
    println!("{}", s.trim().len()); // 5
}

注意点

  • 解引用强制转换只发生在函数参数和方法调用的地方, 不是所有场景都生效
  • 它是零成本的, 不产生新数据, 只是换了个引用
  • 它只转换引用, 不会把 String 变成 str 的所有权

Released under the MIT License.