rust类型转换的实现

 更新时间:2023年12月07日 10:23:25   作者:zy010101  
Rust是类型安全的语言,因此在Rust中做类型转换不是一件简单的事,本文主要介绍了rust类型转换的实现,具有一定的参考价值,感兴趣的可以了解一下

Rust 是类型安全的语言,因此在 Rust 中做类型转换不是一件简单的事。

as转换

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

fn main() {
    cast();
}

// as 进行的显示类型强制转换
fn cast() {
    let n: u8 = 123;
    let m: i32 = n as i32;      // 将u8强制转换为i32类型

    println!("u8({})转i32({})", n, m);

    let a = 12345;         // 整型字面值常量是i32类型
    let b: i8 = a as i8;        // 能容纳更大数值的类型i32转容纳范围较小的i8,存在数据溢出的风险。

    println!("i32({})转i8({})", a, b);

    let c = '我';          // char类型
    let d = c as u32;

    println!("char({})转u32({})", c, d);       

    let f = 100u8;      
    let h = f as char;      // 只有u8才能转char(相当于只支持ASCII码的值和字符转换)

    println!("u8({})转char({})", f, h);

    let f = 123.123;    
    let q = f as i32;
    println!("f64({})转i32({})", f, q);


    let mut num = [1, 2, 3];
    let mut y = num.as_mut_ptr();       // 可变的指针类型
    let mut p = y as usize;                // 把指针转为usize类型
    p += 4;                                       // 指针步进一步(i32类型占4字节,因此加4即可)
    y = p as *mut i32;                            // 将 usize转为指针
    
    unsafe {                                        
        println!("{}", *y);                       // 在unsafe模块中操作指针
    }
}

转换不具有传递性 就算 e as U1 as U2 是合法的,也不能说明 e as U2 是合法的(e 不能直接转换成 U2)。as转换基本上只用于数值类型之间的转换。而且需要注意,当你从可以容纳范围更大的数据类型向可以容纳范围较小的数据类型转换的时候会发生溢出,因此你要人为保证数据转换是正确的。

into和from

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

From

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

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

let s = String::from("qwert");
println!("s={s}");

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

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

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

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

Into

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

使用 Into trait 通常要求指明要转换到的类型,因为编译器大多数时候不能推断它。不过考虑到我们免费获得了 Into,这点代价不值一提。

// 需要指明转换到的类型是Number
let a: Number = 1.into();
println!("My number is {:?}", a);

TryInto和TryFrom

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

pub fn catsing(){
    let b = 123;
    let a: u8 = b.try_into().unwrap();          // try_into
    println!("{a}");

    let b:i32 = 12345;                // 有一点非常奇怪,那就是必须显示声明b的类型,否则编译器无法推断e的类型,导致错误。
    let _a: u8 = match b.try_into() {           // try_into
        Ok(v) => v,
        Err(e) => {
            println!("{:?}", e.to_string());
            0
        }
    };
}

如果我们需要自己实现try_from和try_into方法,那么需要实现TryFrom trait即可。例如:

#[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(())
        }
    }
}

// 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

Display

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

pub fn format_string() {
    use std::fmt;

    struct Circle {
        radius: i32
    }

    impl fmt::Display for Circle {      // 为 Circle 实现 Display trait
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "Circle of radius {}", self.radius)
        }
    }

    let circle = Circle { radius: 6 };
    println!("{}", circle.to_string());     // to_string是由Display trait实现的。
}

当然了,也可以实现ToString trait。例如:

pub fn to_stirng() {
    struct Circle {
        radius: i32
    }

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

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

字符串转数字

只要对目标类型实现了 FromStr trait,就可以用 parse 把字符串转换成目标类型。 标准库中已经给无数种类型实现了 FromStr。如果要转换到用户定义类型,只要手动实现 FromStr 就行。
我们得提供要转换到的类型,这可以通过显示声明类型,或者用 “涡轮鱼” 语法(turbo fish,<>)实现。例如:

pub fn string_to_number(){
    let num = "12345";
    let num = num.parse::<i32>().unwrap();      // turbo fish写法
    println!("{}", num);

    let num = "12345";
    let num: u64 = num.parse().unwrap();            // 显示声明类型写法
    println!("{}", num);
}

点操作符

方法调用的点操作符看起来简单,实际上非常不简单,它在调用时,会发生很多魔法般的类型转换,例如:自动引用、自动解引用,强制类型转换直到类型能匹配等。

假设有一个方法 foo,它有一个接收器(接收器就是 self、&self、&mut self 参数)。如果调用 value.foo(),编译器在调用 foo 之前,需要决定到底使用哪个 Self 类型来调用。现在假设 value 拥有类型 T。再进一步,我们使用完全限定语法来进行准确的函数调用:

  • 首先,编译器检查它是否可以直接调用 T::foo(value),称之为值方法调用
  • 如果上一步调用无法完成(例如方法类型错误或者特征没有针对 Self 进行实现,上文提到过特征不能进行强制转换),那么编译器会尝试增加自动引用,例如会尝试以下调用: <&T>::foo(value) 和 <&mut T>::foo(value),称之为引用方法调用
  • 若上面两个方法依然不工作,编译器会试着解引用 T ,然后再进行尝试。这里使用了 Deref 特征 —— 若 T: Deref<Target = U> (T 可以被解引用为 U),那么编译器会使用 U 类型进行尝试,称之为解引用方法调用
  • 若 T 不能被解引用,且 T 是一个定长类型(在编译器类型长度是已知的),那么编译器也会尝试将 T 从定长类型转为不定长类型,例如将 [i32; 2] 转为 [i32]
  • 若还是不行,那么调用失败

因此点操作符的背后是按照 值方法调用->引用方法调用->解引用方法调用->其它 的顺序来进行调用的。下面是一个例子:

fn do_stuff<T: Clone>(value: &T) {
    let cloned = value.clone();
}

上面例子中 cloned 的类型是什么?首先编译器检查能不能进行值方法调用, value 的类型是 &T,同时 clone 方法的签名也是 &T : fn clone(&T) -> T,因此可以进行值方法调用,再加上编译器知道了 T 实现了 Clone,因此 cloned 的类型是 T。

如果 T: Clone 的特征约束被移除呢?

fn do_stuff<T>(value: &T) {
    let cloned = value.clone();
}

首先,从直觉上来说,该方法会报错,因为 T 没有实现 Clone 特征,但是真实情况是什么呢?

我们先来推导一番。 首先通过值方法调用就不再可行,因为 T 没有实现 Clone 特征,也就无法调用 T 的 clone 方法。接着编译器尝试引用方法调用,此时 T 变成 &T,在这种情况下, clone 方法的签名如下: fn clone(&&T) -> &T,接着我们现在对 value 进行了引用。 编译器发现 &T 实现了 Clone 类型(所有的引用类型都可以被复制,因为其实就是复制一份地址),因此可以推出 cloned 也是 &T 类型。

最终,我们复制出一份引用指针,这很合理,因为值类型 T 没有实现 Clone,只能去复制一个指针了。

下面是一个更复杂的例子:

#[derive(Clone)]
struct Container<T>(Arc<T>);

fn clone_containers<T>(foo: &Container<i32>, bar: &Container<T>) {
    let foo_cloned = foo.clone();
    let bar_cloned = bar.clone();
}

上面代码中,Container<i32> 实现了 Clone 特征,因此编译器可以直接进行值方法调用,此时相当于直接调用 foo.clone,其中 clone 的函数签名是 fn clone(&T) -> T,由此可以看出 foo_cloned 的类型是 Container<i32>

然而,bar_cloned 的类型却是 &Container<T>。这是因为derive 宏最终生成的代码大概如下所示:

impl<T> Clone for Container<T> where T: Clone {
    fn clone(&self) -> Self {
        Self(Arc::clone(&self.0))
    }
}

从上面代码可以看出,派生 Clone 能实现的根本是 T 实现了Clone特征:where T: Clone, 因此 Container<T> 就没有实现 Clone 特征。

编译器接着会去尝试引用方法调用,此时 &Container<T> 引用实现了 Clone,最终可以得出 bar_cloned 的类型是 &Container<T>

当然,也可以为 Container<T> 手动实现 Clone 特征:

impl<T> Clone for Container<T> {
    fn clone(&self) -> Self {
        Self(Arc::clone(&self.0))
    }
}

此时,编译器首次尝试值方法调用即可通过,因此 bar_cloned 的类型变成 Container<T>

参考资料

 到此这篇关于rust类型转换的实现的文章就介绍到这了,更多相关rust类型转换内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Rust中的宏之声明宏和过程宏详解

    Rust中的宏之声明宏和过程宏详解

    Rust中的宏是一种强大的工具,可以帮助开发人员编写可重用、高效和灵活的代码,这篇文章主要介绍了Rust中的宏:声明宏和过程宏,需要的朋友可以参考下
    2023-04-04
  • Rust语言从入门到精通系列之Iterator迭代器深入详解

    Rust语言从入门到精通系列之Iterator迭代器深入详解

    这篇文章主要为大家介绍了Rust语言从入门到精通系列之Iterator迭代器深入详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04
  • Rust 标准库的结构及模块路径详解

    Rust 标准库的结构及模块路径详解

    在 Rust 中,标准库提供了一组核心功能,以帮助开发者执行常见的编程任务,这个路径树可以作为参考,帮助你更好地理解 Rust 标准库的结构和模块之间的关系,本文介绍 Rust 标准库的结构,并提供相应的 use 路径,感兴趣的朋友一起看看吧
    2024-05-05
  • 深入剖析Rust 中的 Move、Copy 和 Clone

    深入剖析Rust 中的 Move、Copy 和 Clone

    在 Rust 编程语言中,move、Copy 和 Clone 是所有权系统的核心概念,它们深刻影响着数据的传递和复制方式,这些机制不仅确保了内存安全,还提供了高效的性能优化手段,本文将深入探讨这些概念的内部机制、使用场景、性能影响以及高级用法,感兴趣的朋友一起看看吧
    2025-05-05
  • 详解Rust调用tree-sitter支持自定义语言解析

    详解Rust调用tree-sitter支持自定义语言解析

    使用Rust语言结合tree-sitter库解析自定义语言需要定义语法、生成C解析器,并在Rust项目中集成,具体步骤包括创建grammar.js定义语法,使用tree-sitter-cli工具生成C解析器,以及在Rust项目中编写代码调用解析器,这一过程涉及到对tree-sitter的深入理解和Rust语言的应用技巧
    2024-09-09
  • Rust中into和from用法及区别介绍

    Rust中into和from用法及区别介绍

    这篇文章主要介绍了Rust中的 into和from使用及区别介绍,into和from是Rust语言中两个用于类型转换的函数,它们分别属于Into和From这两个trait,本文通过实例代码详细讲解,需要的朋友可以参考下
    2023-04-04
  • Rust 连接 PostgreSQL 数据库的详细过程

    Rust 连接 PostgreSQL 数据库的详细过程

    这篇文章主要介绍了Rust 连接 PostgreSQL 数据库的完整代码,本文图文实例代码相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-01-01
  • Rust中字符串与格式化操作方法

    Rust中字符串与格式化操作方法

    Rust提供了多种字符串类型和强大的格式化工具,主要有两种字符串类型:&str(字符串切片)和String(动态字符串),二者均基于UTF-8 编码,本文重点介绍Rust中字符串与格式化的相关知识,感兴趣的朋友一起看看吧
    2025-10-10
  • Rust anyhow 简明示例教程

    Rust anyhow 简明示例教程

    anyhow 是 Rust 中的一个库,旨在提供灵活的、具体的错误处理能力,建立在 std::error::Error 基础上,主要用于那些需要简单错误处理的应用程序和原型开发中,本文给大家分享Rust anyhow 简明教程,一起看看吧
    2024-06-06
  • Rust中不可变变量与const的区别详解

    Rust中不可变变量与const的区别详解

    Rust作者认为变量默认应该是immutable,即声明后不能被改变的变量,这一点是让跨语言学习者觉得很别扭,不过这一点小的改变带来了诸多好处,本节我们来学习Rust中不可变变量与const的区别,需要的朋友可以参考下
    2024-02-02

最新评论