【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中的转换,不仅仅使用方便,更重要的是可以告知阅读者我们在做什么。

相关推荐
混迹网络的权某8 分钟前
蓝桥杯真题——乐乐的序列和(C语言)
c语言·算法·蓝桥杯
良技漫谈10 分钟前
Rust移动开发:Rust在Android端集成使用介绍
android·程序人生·rust·kotlin·学习方法
wheeldown12 分钟前
【数据结构】快速排序
c语言·数据结构·算法·排序算法
神仙别闹12 分钟前
基于 Java 语言双代号网络图自动绘制系统
java·开发语言
猫爪笔记20 分钟前
JAVA基础:单元测试;注解;枚举;网络编程 (学习笔记)
java·开发语言·单元测试
passer__jw76721 分钟前
【LeetCode】【算法】739. 每日温度
算法·leetcode
螺蛳粉只吃炸蛋的走风21 分钟前
网络编程IO多路复用之poll模式
网络·c++·面试·poll·阻塞与非阻塞
aqua353574235824 分钟前
杨辉三角——c语言
java·c语言·数据结构·算法·蓝桥杯
API快乐传递者24 分钟前
用 Python 爬取淘宝商品价格信息时需要注意什么?
java·开发语言·爬虫·python·json
Aurora_th28 分钟前
蓝桥杯 Python组-神奇闹钟(datetime库)
python·算法·职场和发展·蓝桥杯·datetime