【Rust标准库中的convert(AsRef,From,Into,TryFrom,TryInto)】
Rust标准库中的convert(AsRef,From,Into,TryFrom,TryInto)
- 为什么需要convert库
- AsRef(不可变引用:多用于内部字段获取值)
- From/Into Trait | TryFrom/TryInto Trait
- From Trait:
- TryFrom Trait:
- From代码示例
- Into使用方法示例
- 总结
为什么需要convert库
在程序设计时,开发者一般会选择将一系列类型数据打包在一起,并有所限制,一是为了代码的简洁美观和复用,二是为了给予字段访问限制(如我们只通过初始化函数赋予字段值,外部程序需要引用到内部字段但不该修改它)
提到限制,会有开发者想到使用pub 关键字,没错,pub关键字可以将内部字段暴露给外部函数,但是破坏了封装性,且没有限制可变性,有人说我们可以自己写一个函数,也Ok,但是需要注意可变性的限制 ,一般情况下,我们都不希望外部程序可以通过除特定的方法之外修改我们的字段值。
Rust为开发者提供了一揽子转换方法,不论是从基础转为派生,还是反向转换均有trait,使用者只需实现trait,便可使用其中的转换方法并不失去封装性。
AsRef(不可变引用:多用于内部字段获取值)
假设有这样一种需求,某论坛需要获取登陆者的电话号码,以便在后续论坛举办活动时给会员发去活动邀请,所以我们就需要挑选合适的方式在代码中储存信息以及分析合理性。
- 我们需要一个struct元组来描述用户的电话号码(这里涉及到新类型模式,不展开了,仅以简单示例):
//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方法用以表示发送短信通知:
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:
//在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有如下特点:
- 涉及到的转换不可以失败。
- 转换必须无损,如不丢失数据
- 转换必须保值,即不丢失精度
- 必须式是显而易见的转换,如 AuthError—> ServerError
//仅作代码示例
#[derive(Debug)]
enum ServerError {//总类型
DatabaseError(String),//细分类型
NetworkError(std::io::Error),
TimeoutError(VarError),
AuthError(Error),
}
ps: 如果不符合上述,则需要使用TryFrom,同样的,实现了TryFrom等于实现了TryInto。
From Trait:
pub trait From<T>: Sized {
// Required method
fn from(value: T) -> Self;
}
//其直接返回类型。
TryFrom Trait:
pub trait TryFrom<T>: Sized {
type Error;
// Required method
fn try_from(value: T) -> Result<Self, Self::Error>;
}
//其返回Result,允许失败。
From代码示例
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
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中的转换,不仅仅使用方便,更重要的是可以告知阅读者我们在做什么。