【Rust标准库中的convert(AsRef,From,Into,TryFrom,TryInto)】

Rust标准库中的convert(AsRef,From,Into,TryFrom,TryInto)


为什么需要convert库

在程序设计时,开发者一般会选择将一系列类型数据打包在一起,并有所限制,一是为了代码的简洁美观和复用,二是为了给予字段访问限制(如我们只通过初始化函数赋予字段值,外部程序需要引用到内部字段但不该修改它)

提到限制,会有开发者想到使用pub 关键字,没错,pub关键字可以将内部字段暴露给外部函数,但是破坏了封装性,且没有限制可变性,有人说我们可以自己写一个函数,也Ok,但是需要注意可变性的限制 ,一般情况下,我们都不希望外部程序可以通过除特定的方法之外修改我们的字段值。

Rust为开发者提供了一揽子转换方法,不论是从基础转为派生,还是反向转换均有trait,使用者只需实现trait,便可使用其中的转换方法并不失去封装性。

AsRef(不可变引用:多用于内部字段获取值)

假设有这样一种需求,某论坛需要获取登陆者的电话号码,以便在后续论坛举办活动时给会员发去活动邀请,所以我们就需要挑选合适的方式在代码中储存信息以及分析合理性。

  1. 我们需要一个struct元组来描述用户的电话号码(这里涉及到新类型模式,不展开了,仅以简单示例):
rust 复制代码
//UserInfo.rs
#[derive(Debug)]
pub struct Phone(String);
//我们实现了简单的new函数,并包含简单的号码正确性验证。
impl Phone {
    pub fn new(phone_number: &str) -> Result<Self, &'static str> {
        if phone_number.len() == 11 {
            Ok(Self(phone_number.to_string()))
        } else {
            Err("Invalid phone number")
        }
    }
}
//并将其封装到包mod.rs
pub(crate) mod UserInfo;

2.我们在main.rs,写一个sendmsg方法用以表示发送短信通知:

rust 复制代码
mod user_info;
use std::error::Error;

use user_info::UserInfo::Phone;

fn sendmsg(phonenum: &str) -> Result<String, Box<dyn Error>> {
    // Simulate a call to the phone number
    // For simplicity, we just return the number as a string
    Ok(phonenum.to_string())
}
fn main() {
    let result = Phone::new("13324533333");
    match result {
        Ok(phone) => sendmsg(phone.0),
        Err(e) => todo!(),
    };
}
//哪里会有问题?

没错,在sendmsg(phone.0) 时会有如下报错,field 0 of Phone is private

不论是将Phone中的String设置为pub,还是使用可变引用都破坏了我们代码的封装性。

正确地方法:实现trait AsRef:

rust 复制代码
//在UserInfo.rs中添加如下实现
impl AsRef<str> for Phone {
    fn as_ref(&self) -> &str {
        &self.0
    }
}

//main.rs中使用入下调用
let result: Result<Phone, &str> = Phone::new("13324533333");
    let _ = match result {
        Ok(phone) => sendmsg(phone.as_ref()),
        Err(_e) => todo!(),
    };

如此,我们既不破坏封装性,又可以使用Rust convert的系列方法。

From/Into Trait | TryFrom/TryInto Trait

用以值到值的转换,简单来说,From提供细分内部类型向外部总类型转换,Into可以理解为为了适配孤儿规则而存在的,当前的版本1.82.0一般情况下不会有人主动实现Into,由于Rust的一揽子trait ,实现了From就等于实现了Into,非常方便。

不论是实际应用上,还是Rust中的举例来讲,From确实非常适合工程下的错误处理。

From有如下特点:

  1. 涉及到的转换不可以失败。
  2. 转换必须无损,如不丢失数据
  3. 转换必须保值,即不丢失精度
  4. 必须式是显而易见的转换,如 AuthError---> ServerError
rust 复制代码
//仅作代码示例
#[derive(Debug)]
enum ServerError {//总类型
    DatabaseError(String),//细分类型
    NetworkError(std::io::Error),
    TimeoutError(VarError),
    AuthError(Error),
}

ps: 如果不符合上述,则需要使用TryFrom,同样的,实现了TryFrom等于实现了TryInto。

From Trait:

rust 复制代码
pub trait From<T>: Sized {
    // Required method
    fn from(value: T) -> Self;
}
//其直接返回类型。

TryFrom Trait:

rust 复制代码
pub trait TryFrom<T>: Sized {
    type Error;
    // Required method
    fn try_from(value: T) -> Result<Self, Self::Error>;
}
//其返回Result,允许失败。

From代码示例

rust 复制代码
use std::{
    env::VarError,
    fmt::{Display, Error},
};
//仅作代码示例
#[derive(Debug)]
enum ServerError {
    DatabaseError(String),
    NetworkError(std::io::Error),
    TimeoutError(VarError),
    AuthError(Error),
}
impl Display for ServerError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ServerError::DatabaseError(err) => write!(f, "Database error: {}", err),
            ServerError::NetworkError(err) => write!(f, "Network error: {}", err),
            ServerError::TimeoutError(err) => write!(f, "Timeout error: {}", err),
            ServerError::AuthError(err) => write!(f, "AuthError error: {}", err),
        }
    }
}
impl From<String> for ServerError {
    fn from(err: String) -> Self {
        ServerError::DatabaseError(err)
    }
}

impl From<std::io::Error> for ServerError {
    fn from(err: std::io::Error) -> Self {
        ServerError::NetworkError(err)
    }
}
impl From<Error> for ServerError {
    fn from(err: Error) -> Self {
        ServerError::AuthError(err)
    }
}

impl From<VarError> for ServerError {
    fn from(err: VarError) -> Self {
        ServerError::TimeoutError(err)
    }
}

fn handle_server_error() -> Result<(), String> {
    Err(String::from("handle_server_error"))
}
fn handle_server_error1() -> Result<(), std::io::Error> {
    Ok(())
}
fn handle_server_error2() -> Result<(), VarError> {
    Ok(())
}
fn handle_server_error3() -> Result<(), Error> {
    Ok(())
}

fn func() -> Result<(), ServerError> {
    handle_server_error()?;
    handle_server_error1()?;
    handle_server_error2()?;
    handle_server_error3()?;
    Ok(())
}

fn main() {
    match func() {
        Ok(_) => println!("Success"),
        Err(e) => eprintln!("Error: {:?}", e),
    }
}

如此,开发者既可以细分错误类型,并定制化输出内容,又可以统一result类型,并使用【?】将error向上抛出

同时,Rust也提供了thiserror,anyhow等错误处理包,通过扩展宏标记后,会生成大部分的样板代码,同时通过trace等方式,将上下文串联起来更加方便的排查问题。

Into使用方法示例

此示例代码来源于Rust By Example

rust 复制代码
use std::convert::Into;

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

impl Into<Number> for i32 {
    fn into(self) -> Number {
        Number { value: self }
    }
}

fn main() {
    let int = 5;
    // Try removing the type annotation
    let num: Number = int.into();
    println!("My number is {:?}", num);
}

总结

Rust中的转换,不仅仅使用方便,更重要的是可以告知阅读者我们在做什么。

相关推荐
南宫乘风1 分钟前
深入浅出 WebSocket:构建实时数据大屏的高级实践
网络·websocket·网络协议
是老余1 分钟前
Java三大特性:封装、继承、多态【详解】
java·开发语言
小馒头学python3 分钟前
【Python爬虫五十个小案例】爬取豆瓣电影Top250
开发语言·爬虫·python
尘浮生1 小时前
Java项目实战II基于微信小程序的南宁周边乡村游平台(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·微信小程序·小程序·maven
wangjing_05223 小时前
C语言练习.if.else语句.strstr
c语言·开发语言
Tony_long74833 小时前
Python学习——字符串操作方法
开发语言·c#
SoraLuna4 小时前
「Mac玩转仓颉内测版26」基础篇6 - 字符类型详解
开发语言·算法·macos·cangjie
出逃日志4 小时前
JS的DOM操作和事件监听综合练习 (具备三种功能的轮播图案例)
开发语言·前端·javascript
前端青山4 小时前
React事件处理机制详解
开发语言·前端·javascript·react.js
雨中rain4 小时前
贪心算法(2)
算法·贪心算法