改进rust代码的35种具体方法-类型(六)-了解类型转换

上一篇文章


一般来说,Rust不会在类型之间进行自动转换。这包括积分类型,即使转换是“安全的”:

        let x: u32 = 2;
        let y: u64 = x;
error[E0308]: mismatched types
  --> casts/src/main.rs:69:22
   |
69 |         let y: u64 = x;
   |                ---   ^ expected `u64`, found `u32`
   |                |
   |                expected due to this
   |
help: you can convert a `u32` to a `u64`
   |
69 |         let y: u64 = x.into();
   |                       +++++++

Rust类型转换分为三类:

  • 手册:通过实现FromInto特征提供的用户定义类型转换
  • 半自动:使用as关键字在值之间显式转换
  • 自动:隐式胁迫进入新类型。

后两个不适用于用户定义类型的转换(有几个例外),因此本项目的大部分内容将侧重于手动转换——编译器错误消息也指向手动转换。

然而,项目末尾的部分讨论了铸造和胁迫——包括它们可以适用于用户定义类型的例外情况。

用户定义的类型转换

与该语言的其他功能(上一篇)一样,在不同用户定义类型的值之间执行转换的能力被封装为标准特征——或者更确切地说,作为一组相关的通用特征。

表达转换类型值能力的四个相关特征是:

  • From<T>:这种类型的项目可以从T类型的项目构建。
  • TryFrom<T>:这种类型的项目有时可以从T型项目构建。
  • Into<T>:这种类型的项目可以转换为T型项目。
  • TryInto<T>:这种类型的项目有时可以转换为T型项目。

鉴于之前中关于在类型系统中表达事物的讨论,发现与Try...变体的区别并不奇怪,唯一的特征方法返回一个Result,而不是一个有保证的新项目。Try...特征定义还需要一个关联类型,该类型给出了故障情况下发出的错误E的类型。

因此,第一条建议是实现(只是)Try…如果转换有可能失败,根据第4项。另一种选择是忽略错误的可能性(例如使用.unwrap()),但这需要经过深思熟虑的选择,在大多数情况下,最好将选择留给调用者。

类型转换特征具有明显的对称性:如果T类型可以intoU型,这不就等同于通过fromT类型项目进行转换来创建U型项目吗?

确实如此,它导致了第二条建议:实施转换的From特征。Rust标准库必须从两种可能性中只选择一种,以防止系统在头晕目眩的圈子里盘旋1,它从从From实现自动提供Into的一边。

如果你正在使用这两个trait中的一个,作为你自己的一个新泛型的trait绑定,那么建议就反过来了:使用Into trait作为trait绑定。这样,直接实现Into的东西和只直接实现From的东西都能满足边界。

From和Into的文档强调了这种自动转换,但也值得阅读标准库代码的相关部分,这是一个全面的trait实现:

impl<T, U> Into<U> for T
where
    U: From<T>,
{
    fn into(self) -> U {
        U::from(self)
    }
}

将特征规范翻译成文字可以帮助理解更复杂的特征界限;在这种情况下,这相当简单:“每当U已经实现From<T>,我可以为T型实现Into<U>”。

标准库还包括标准库类型的这些转换特征的各种实现。正如您所期望的,有用于安全积分转换的From实现(From<u32> for u64)和转换不安全时的TryFrom实现(TryFrom<u64> for u32)。

除了上面显示的Into版本外,还有其他各种一揽子特征实现,您可以通过搜索impl<T> From<T> for ...找到这些实现。这些几乎都是forsmart指针类型,允许从它持有的类型实例自动构造智能指针,因此接受智能指针参数的方法也可以用普通的旧项调用;下文和项目9中对此有更多内容。

TryFrom特征还具有对任何已经向相反方向实现Into特征的类型的全面实现——它自动包括(如上所述)向同一方向实现From的任何类型。换句话说,如果您可以无误地将T转换为U,您也可以错误地从T中获取U;由于这种转换将始终成功,相关的错误类型是2,该错误类型被命名为Infallible

From的一个非常具体的通用实现,即反射实现

impl<T> From<T> for T {
    fn from(t: T) -> T {
        t
    }
}

翻译成文字,这只是说“给一个T,我可以得到一个T”。这是如此明显的“嗯,嗯”,值得停下来了解为什么这很有用。

考虑一个简单的新类型struct(下一篇)和一个在上面操作的函数(忽略这个函数最好用方法表达):

/// Integer value from an IANA-controlled range.
#[derive(Clone, Copy, Debug)]
pub struct IanaAllocated(pub u64);

/// Indicate whether value is reserved.
pub fn is_iana_reserved(s: IanaAllocated) -> bool {
    s.0 == 0 || s.0 == 65535
}

此函数可以通过实例调用struct

    let s = IanaAllocated(1);
    println!("{:?} reserved? {}", s, is_iana_reserved(s));
    // output: "IanaAllocated(1) reserved? false"

但即使From<u64>是为新类型包装器实现的

impl From<u64> for IanaAllocated {
    fn from(v: u64) -> Self {
        Self(v)
    }
}

不能为u64值直接调用该函数

error[E0308]: mismatched types
  --> casts/src/main.rs:82:29
   |
82 |         if is_iana_reserved(42) {
   |                             ^^ expected struct `IanaAllocated`, found integer

然而,该函数的通用版本接受(并显式转换)任何令人满意的内容Into<IanaAllocated>

    pub fn is_iana_reserved<T>(s: T) -> bool
    where
        T: Into<IanaAllocated>,
    {
        let s = s.into();
        s.0 == 0 || s.0 == 65535
    }

允许此用途:

        if is_iana_reserved(42) {

随着此特征的绑定到位,From<T>的反射特征实现更有意义:这意味着通用函数可以处理已经是IanaAllocated实例的项目,不需要转换。

这种模式还解释了为什么(以及如何)Rust代码有时似乎在类型之间进行隐式转换:From<T>实现和Into<T>特征边界的组合导致代码似乎在调用站点神奇地转换(但仍然在掩护下进行安全、显式转换),当与参考类型及其相关转换特征相结合时,这种模式变得更加强大;

Casts

Rust包括as关键字,用于在某些对类型之间执行显式转换

可以以这种方式转换的类型对是一个相当有限的集合,它包含的唯一用户定义类型是“类似C”的enum(那些只有关联的整数值)。不过,包括了一般积分转换,提供了into()的替代方案:

    let x: u32 = 9;
    let y = x as u64;
    let z: u64 = x.into();

as版本还允许有损转换3

    let x: u32 = 9;
    let y = x as u16;

这将被from/into版本拒绝:

error[E0277]: the trait bound `u16: From<u32>` is not satisfied
   --> casts/src/main.rs:124:20
    |
124 |     let y: u16 = x.into();
    |                    ^^^^ the trait `From<u32>` is not implemented for `u16`
    |
    = help: the following implementations were found:
              <u16 as From<NonZeroU16>>
              <u16 as From<bool>>
              <u16 as From<u8>>
              <f32 as From<i16>>
            and 71 others
    = note: required because of the requirements on the impl of `Into<u16>` for `u32`

为了一致性和安全性,您应该更喜欢from/into转换到as转换,除非您理解并需要精确的转换语义(例如C互操作性)。

Coercion

上一节中描述的显式as施法是编译器将默默执行的隐式胁迫的超集:任何胁迫都可以用显式as强制执行,但反过来是不正确的。特别是,上一节中执行的积分转换不是强制的,因此总是需要。

大多数胁迫涉及以对程序员来说合理和方便的方式对指针和引用类型进行静默转换,例如:

  • 将可变引用转换为不可变引用(以便您可以使用&mut T作为接受&T的函数的参数)
  • 将引用转换为原始指针(这并不unsafe——不安全发生在你愚蠢到使用原始指针的地方)
  • 将碰巧不捕获任何变量的闭包转换为裸函数指针
  • 数组转换为切片
  • 将具体项目转换为特征对象,对于具体项目实现的特征
  • 将项目寿命4转换为“较短”的寿命。

只有两个胁迫,其行为可以受到用户定义类型的影响。第一个是当用户定义的类型实现DerefDerefMut特征时。这些特征表明,用户定义的类型正在充当某种智能指针,在这种情况下,编译器将强迫对智能指针项的引用,使其成为对智能指针包含的类型项目的引用(由其Target表示)。

当具体项目转换为特征对象时,用户定义类型的第二次强制就会发生。此操作构建指向项目的胖指针;此指针是胖的,因为它既包括指向内存中项目位置的指针,也包括指向具体类型实现特征的vtable的指针。


1:更恰当地称为特征一致性规则

2:目前-这可能会被替换为!在未来版本的Rust中 ! "never" type 

3:在Rust中允许有损转换可能是一个错误,并且已经围绕试图消除这种行为进行了讨论。

4:Rust将这些转换称为“子类型”,但与面向对象语言中使用的“子类型”的定义完全不同。

下一篇文章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值