笔记- Rust类型系统4: 类型转换

473 阅读5分钟

类型转换

在编程语言中,类型转换分为 隐式类型转换显式类型转换

Rust 是显式类型转换语言,只包含少部分的 隐式类型转换。

Deref 解引用

Rust 中的隐式类型转换,只有自动解引用

自动解引用,是为了方便开发者使用 智能指针,比如: Box<T>,Rc<T>,String 等类型。

自动解引用

一般来说,引用使用 & 操作符,解引用使用 * 操作符。

自动解引用是编译器来做的,用户可以通过实现 Deref trait 来自定义 自动解引用的行为

Deref 有一个特性是强制隐式转换,规则如下: 如果一个类型 T 实现了 Deref<Target=U>,则该类型 T 的引用(或智能指针) 在应用的时候会被自动转换为类型 U。

DerefMut 和 Deref 类似,只不过它是返回可变引用的,Deref 中包括关联类型 Target,它表示解引用之后的目标类型。

// Deref 和 MutDeref trait 的源码(简化)

pub trait Deref {
    type Target: ?Sized;
    fn deref(&self) -> &Self::Target;
}

pub trait DerefMut: Deref {
    fn deref_mut(&mut self) -> &mut Self::Target;
}

// String 实现的自动解引用
impl ops::Deref for String {
    type Target = str;
    fn deref(&self) -> &str {
        unsafe { str::from_utf8_unchecked(&self.vec) }
    }
}

自动解引用样例:

fn main() {
    let a: String = "hello ".to_string();
    let b: String = "world".to_string();
    // 此处将 &String 自动解引用为 &str ,因为 String 实现了 Deref<Target=&str>,见上一个代码片段。
    let c: String = a + &b;  
    println!("c = {}", c);   // c = hello world
}

除了 String 类型,标准库中常见的其他类型,如 Vec<T>、Box<T>、Rc<T>、Arc<T> 等均实现了 Deref。

实现 Deref 的目的,是为了简化编程,样例如下:

fn main() {
    // Vec<T> 实现了 Deref
    let v: Vec<i32> = vec![1, 2, 3];
    foo(&v);
    foo1(&v);

    // Rc 指针实现了 Deref
    let x: Rc<&str> = Rc::new("hello");
    println!("x.chars() = {:?}", x.chars());
}

fn foo(v: &Vec<i32>) {
    println!("v[0] = {:?}", v[0]);
}

fn foo1(v: &[i32]) {
println!("v[0] = {:?}", v[0]);
}

手动解引用

某类型 和其 解引用目标类型 中包含了相同的方法时,编译器无法判断要使用哪个,此时需要 手动解引用

fn main() {
    let x: Rc<&str> = Rc::new("hello");
    let y: Rc<&str> = x.clone();
    let z: &str = (*x).clone();
}

另外,match 引用时也需要手动解引用,如下所示:

fn main() {
    let x = "hello".to_string();
    match &(*x) {
        "hello" => {
            println!("hello")
        }
        _ => {
            println!("none");
        }
    }
}

在上述情况下,需要手动解引用吧 String 类型转为 str,进而转为 &str 类型。

  • match x.deref(),直接调用 deref 方法,需要 use std::ops::Deref

  • match x.as_ref(),String 类型提供了 as_ref 方法来返回一个 &str 类型,改方法定义在 AsRef trait 中。

  • match x.borrow(),行为和 AsRef 类似,方法定义在 std::borrow::Borrow 中。

  • match &*x,使用 "解引用" 操作符 * ,将 String 转换为 str,然后在用 "引用"操作符 &,转为 &str 类型。

  • match &x[..], String 类型的 index 操作可返回 &str 类型。

as 操作符

基本类型转换

as 操作符最常用的场景就是转换 Rust 中的基本数据类型。as 操作符不支持重载。

原生类型使用 as 操作符进行类型转换的样例。

  • 使用 as 操作符时,短类型 转换为 长类型 的时候是没问题的,反过来的时候,会做截断处理。
fn main() {
    let a = 1u32;
    let b = a as u64;
    let x = 1u64;
    let y = x as u32;
    println!("a = {}, b = {}", a, b); // a = 1, b = 1
    println!("x = {}, y = {}", x, y); // x = 1, y = 1

    let m = std::u32::MAX;
    let n = m as u16;
    println!("m = {}, n = {}", m, n); // m = 4294967295, n = 65535

    let e = -1i32;
    let f = e as u32;
    println!("e = {}, f = {}", e, f); // e = -1, f = 4294967295
}

无歧义完全限定语法

结构体 S 实现了 trait A 和 B 相同名称的方法,可通过 无歧义完全限定语法 指明调用哪个实现,两种方法:

  • 第一种是 当做 trait 的静态函数来调用。形如 A::test(&s, 1); B::test(&s, 1);

  • 第二种是 使用 as 操作符, <S as A>::test(&s, 3); <S as B>::test(&s, 3);

方式2更明确一些,指明了结构体信息。

#[derive(Debug)]
struct S(i32);

trait A {
    fn test(&self, i: i32);
}

trait B {
    fn test(&self, i: i32);
}

impl A for S {
    fn test(&self, i: i32) {
        println!("From A, S = {:?}, i = {}", &self, i);
    }
}

impl B for S {
    fn test(&self, i: i32) {
        println!("From B, S = {:?}, i = {}", &self, i);
    }
}

fn main() {
    let s = S(6);
    // s.test(3); // compile error

    A::test(&s, 1);
    B::test(&s, 1);

    <S as A>::test(&s, 3);
    <S as B>::test(&s, 3);
}

类型和子类型相互转换

as 转换还可用于 类型 和 子类型 之间的转换。

尽管 Rust 没有结构体继承的概念,它却有子类型机制。在 Rust 中,子类型是针对生命周期存在的。

生命周期是代码的作用域,所以我们可以根据它们相互包含的关系判断他们的继承关系。

比如 &'static str 类型 是 &'a str 类型的子类型, 'a 和 'static 都是声明周期标记,其中 'a 是泛型标记, 是 &str 的通用形式,'static 则是特指 静态生命周期的 &str 字符串。

fn main() {
    let a: &'static str = "hello";
    let b: &str = a as &str;
    let c: &'static str = b as &'static str;
}

From 和 Into

FromInto 是定义于 std::convert 模块中的两个 trait。它们定义了 from 和 into 两个方法。

pub trait From<T>: Sized {
    fn from(_: T) -> Self;
}

pub trait Into<T>: Sized {
    fn into(self) -> T;
}

使用 Into trait 的样例:

#[derive(Debug)]
struct Person {
    name: String,
}

impl Person {
    fn new<T: Into<String>>(in_obj: T) -> Self {
        Person {
            name: in_obj.into(),
        }
    }
}
fn main() {
    let a: String = "hello".to_string();
    let other_string: String = String::from("hello");
    println!("other_string = {}", other_string);

    let person = Person::new("xiaoming");
    println!("person = {:?}", person); // person = Person { name: "xiaoming" }
}

关于 Into 的一条默认规则: 如果类型 U 实现了 From<T>,则 T 类型实例调用 into 方法就可以转换为 类型 U。

fn main() {
    let aa = "strstr";
    let bb: String = aa.into();
}