zoukankan      html  css  js  c++  java
  • Rust 中的类型转换

    1. as 运算符

    as 运算符有点像 C 中的强制类型转换,区别在于,它只能用于原始类型(i32i64f32
    f64u8u32char 等类型),并且它是安全的

    在 Rust 中,不同的数值类型是不能进行隐式转换的,比如:

     let b: i64 = 1i32;
    

    会出现编译错误,提示无法进行类型转换。

    error[E0308]: mismatched types
     --> srcmain.rs:2:18
        |
    2   |     let b: i64 = 1i32;
        |                  ^^^^ expected i64, found i32
    help: change the type of the numeric literal from `i32` to `i64`
    

    这时可以使用as 进行转换。

    let b: i64 = 1i32 as i64;
    
    • 为什么它是安全的?

      尝试以下代码:

      let b = 1i32 as char;
      

      编译器错误:

      error[E0604]: only `u8` can be cast as `char`, not `i32`
      --> srcmain.rs:2:13
          |
      2   |     let b = 1i32 as char;
          |             ^^^^^^^^^^^^
      

      可见在不相关的类型之间,Rust 会拒绝转换,这也避免了运行时错误。

    2. Trait From<T>Into<T>

    上文说到,as 运算符之能在原始类型之间进行转换,那么对于 Struct 和 Enum 这样的类型该如何进行转换呢? 这就是我们这节的内容 From<T>Into<T>

    先来看一看这两个 Trait 的结构。

    pub trait From<T> {
        fn from(T) -> Self;
    }
    pub trait Into<T> {
        fn into(self) -> T;
    }
    

    很简单,From<T> 有一个 from 方法,Into<T> 有一个 into 方法。

    一般来说,我们应该尽量优先选择实现 From<T> 而不是 Into<T> ,因为当你为 U 实现 From<T> ,这意味着你同时也为 T 隐式实现了 Into<U>

    来看个例子

    fn main() {
        println!("Hello, world!");
        let b: Complex = 1.into();
        println!("{:?}", b);
    }
    #[derive(Debug)]
    struct Complex {
        re: i32,
        im: i32
    }
    
    impl From<i32> for Complex{
        fn from(re: i32) -> Self {
            Complex{
                re,
                im:0
            }
        }
    }
    

    当我为 Complex 实现 From<i32> 后,我也可以在 i32 上使用 into 方法,转换到 Complex

    原始类型实现了与 as 转换相对应的 From<T>Into<T>

    当你为 U 实现 From<T> 之后,你要确保这个转换一定能成功,如若有失败的可能,你应该选择为 U 实现 TryFrom<T>

    • 什么时候该使用 Into<T>

      Into<T> 被设计出来,总有该用到的地方。那什么时候该使用呢?

      先复习一下 Rust 中的 孤儿原则

      在声明trait和impl trait的时候,Rust规定了一个Orphan Rule(孤儿规则):impl块要么与trait的声明在同一个的crate中,要么与类型的声明在同一个crate中。

      也就是说,不能在一个crate中,针对一个外部的类型,实现一个外部的trait。

      因为在其它的crate中,一个类型没有实现一个trait,很可能是有意的设计。

      如果我们在使用其它的crate的时候,强行把它们“拉郎配”,是会制造出bug的。

      比如说,我们写了一个程序,引用了外部库lib1和lib2,lib1中声明了一个trait T,lib2中声明了一个struct S ,我们不能在自己的程序中针对S实现T。

      这也意味着,上游开发者在给别人写库的时候,尤其要注意。

      一些比较常见的标准库中的 trait,比如 Display Debug ToString Default 等,应该尽可能地提供好。

      否则,使用这个库的下游开发者,是没办法帮我们把这些 trait 实现的。

      同理,如果是匿名impl,那么这个impl块必须与类型本身存在于同一个模块中。

      来自 F001 https://zhuanlan.zhihu.com/p/21568827

      显然, From<T> 不属于当前 crate ,当你要实现当前 crate 中的类型 T 转换到其他 crate 中的类型 U 时,如果选择为 U 实现 From<T> ,由于孤儿原则,编译器会阻止你这么做。这时我们就可以选择为 T 实现 Into<U>

      注意,和 From<T> 不同,实现 Into<U> 之后并不会隐式实现 From<T> ,这点需特别注意。

    • From<T> 的妙用

      回忆一下 Rust 的 ? 操作符,它被用于 返回值为 Result<T,E> 或者 Option<T> 的函数。回想一下,它是如何处理 Err(E) 的。

      fn apply() -> Result<i32,i32> {
          Err(1)
      }
      fn main() -> Result<(),i64> {
          let a = apply()?;
          Ok(())
      }
      

      上面的例子是可以通过编译的,既然 Rust 中的数值类型是不能隐式转换的,那么,当返回 Err(i32) 时是如何转换到 Err(i64) 的呢?这其实是一个 Rust 的语法糖。展开后的代码类似于下面:

      fn apply() -> Result<i32,i32> {
          Err(1)
      }
      fn main() -> Result<(),i64> {
          let a = match apply() {
              Ok(v) => v,
              Err(e) => return Err(i64::from(e)),
          };
          Ok(())
      }
      

      也就是说,Rust 会自动调用目标类 from 方法进行转换。

    3. 解引用强制多态

    这次先看一个例子:

    fn print(message: &str) {
        println!("{}",message);
    }
    fn main() {
        let message: String = "message".to_string();
        print(&message);
    }
    

    print 的形参是 &str 类型,然而在 main 中,我传递却是一个 &String 类型的实参。明显,这两个类型不相同!!Rust 为什么会通过这样的代码呢?

    没错,这就是 Rust 的 解引用强制多态。

    首先,需要了解一个 Deref Trait 。

    #[lang = "deref"]
    pub trait Deref {
    
        type Target: ?Sized;
    
        #[must_use]
        fn deref(&self) -> &Self::Target;
    }
    

    deref 方法返回一个 &Target 类型的引用。

    回忆一下 Rust 中的解引用语法,当 ref 是一个引用或智能指针时,我们可以使用 *ref 的方式解引用。这是类似一个语法糖,对于 *ref 这种写法,写全应该时 *(ref.deref())

    回想 Box<T> 的使用,Box<T> 实现了 Deref ,它的 deref 方法返回 &T 的引用,然后使用解引用运算符 * ,我们顺利拿到一个 T 类型的数据。也就是,你可以通过实现 Deref 以重载解引用运算符。

    Deref 和这节的内容有什么关系呢?

    T 实现了 Deref<Target=U> 时,对于需要 &U 的地方,你可以提供一个 &T 类型的数据,Rust会为你自动调用 deref 方法,而这个过程可以重复多次。

    比如,我自定义类型 P 实现了 Deref<Target=String> ,那么可以把 &P 类型变量传递给一个 &str 类型变量。&P -> &String -> &str ,伪代码: &P.deref().deref()

    回到这节开头的例子,print(&message) 相当于 print((&message).deref()) ,正好是一个 &str 类型。

  • 相关阅读:
    poj 2411 Mondriaan's Dream 骨牌铺放 状压dp
    zoj 3471 Most Powerful (有向图)最大生成树 状压dp
    poj 2280 Islands and Bridges 哈密尔顿路 状压dp
    hdu 3001 Travelling 经过所有点(最多两次)的最短路径 三进制状压dp
    poj 3311 Hie with the Pie 经过所有点(可重)的最短路径 floyd + 状压dp
    poj 1185 炮兵阵地 状压dp
    poj 3254 Corn Fields 状压dp入门
    loj 6278 6279 数列分块入门 2 3
    VIM记事——大小写转换
    DKIM支持样本上传做检测的网站
  • 原文地址:https://www.cnblogs.com/ywxt/p/11801778.html
Copyright © 2011-2022 走看看