

Rust中的向下转型
source link: https://jasonkayzk.github.io/2023/12/13/Rust%E4%B8%AD%E7%9A%84%E5%90%91%E4%B8%8B%E8%BD%AC%E5%9E%8B/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在 Java 等存在继承的编程语言中,有时候会需要将一个父类或接口类转为一个具体的子类,这时候需要用到向下转型(downcast);
而在 Rust 中,有时候也需要将一个具体的 Trait 对象转为一个具体的类型,此时需要用到 Any Trait;
本文讲述了如何在 Rust 中实现向下转型(downcast);
Rust中的向下转型
Rust中的向下转型需要使用到 Any Trait;
关于 Any,我之前的文章中讲过:
不过上一篇文章更多的是讲解如何通过 Any 来获取变量类型,或转换为一般的类型,不涉及到 Trait 变量;
这篇将会探讨 Trait 对象的向下转型;
向下转型例子
下面是一个向下转型的例子:
examples/2_trait_downcast.rs
use std::any::Any;
#[derive(Default)]
struct Test {
age: i32,
}
trait Custom: AsAny {
fn hello(&self) -> String;
}
trait AsAny {
fn as_any(&self) -> &dyn Any;
}
impl AsAny for Test {
fn as_any(&self) -> &dyn Any {
self
}
}
impl Custom for Test {
fn hello(&self) -> String {
String::from("hello")
}
}
fn main() {
let test = Test { age: 1 };
let custom: Box<dyn Custom> = Box::new(test);
println!("age: {}", custom.as_any().downcast_ref::<Test>().unwrap().age)
}
Test 为我们的结构体,Custom 为对应的 Trait;
同时我们为 Test 实现了 AsAny 来将结构体转换为 Any 类型;
在 main 函数中,我们先创建了一个 Test 对象,然后使用 Box 包装并转为了一个 Trait 对象;
最后,我们再使用 custom.as_any().downcast_ref::<Test>()
来进行向下转换将一个 Trait 对象转换为了一个 Test 具体类型;
以上就是在 Rust 中实现向下转换的方法;
更复杂的例子
来看下面这个例子:
examples/3_trait_downcast2.rs
use std::any::{Any, TypeId};
#[derive(Default)]
struct Test1 {
age: i32,
}
#[derive(Default)]
struct Test2 {
salary: i32,
}
trait Custom: AsAny {
fn hello(&self) -> String;
}
trait AsAny {
fn as_any(&self) -> &dyn Any;
fn type_id(&self) -> TypeId;
}
impl AsAny for Test1 {
fn as_any(&self) -> &dyn Any {
self
}
fn type_id(&self) -> TypeId {
TypeId::of::<Self>()
}
}
impl Custom for Test1 {
fn hello(&self) -> String {
String::from("hello from test1")
}
}
impl AsAny for Test2 {
fn as_any(&self) -> &dyn Any {
self
}
fn type_id(&self) -> TypeId {
TypeId::of::<Self>()
}
}
impl Custom for Test2 {
fn hello(&self) -> String {
String::from("hello from test2")
}
}
fn main() {
let mut v: Vec<Box<dyn Custom>> = vec![];
let test1 = Box::new(Test1 { age: 1 });
let test2 = Box::new(Test2 { salary: 2 });
v.push(test1);
v.push(test2);
for item in v {
println!("{}", item.hello());
let any_item = item.as_any();
if any_item.type_id() == TypeId::of::<Test1>() {
println!("age: {}", any_item.downcast_ref::<Test1>().unwrap().age)
}
if any_item.type_id() == TypeId::of::<Test2>() {
println!("salary: {}", any_item.downcast_ref::<Test2>().unwrap().salary)
}
}
}
我们创建了 Test1、Test2 两个结构体,并为他们实现了 Custom、AsAny Trait;
注意:AsAny Trait 中添加了 type_id 方法,用来获取结构体对象的实际类型!
在 main 函数中,我们创建了 Test1、Test2 两个类型的对象,并将他们放入到 Vec 中;
在 for 循环中,我们首先调用了对象共有的 Custom Trait 中的方法;
随后通过 type_id 对具体类型进行判断,并转换,完成了向下转型!
可以看到,虽然 Rust 中没有继承的概念,但是也可以完成和面向对象类似的效果!
使用范型简化实现
在上面的代码中可以看到,我们需要为每个具体的类型实现一遍 AsAny Trait,非常麻烦;
我们可以使用范性做简化!
通过给 AsAny Trait 提供默认的实现,让其他 Trait 继承 AsAny Trait 来直接实现!
代码如下:
boost-rs/src/types/as_any.rs
//! This library provides some utility traits to make working with [`Any`] smoother.
//! This crate contains similar functionality to the `downcast` crate, but simpler,
use std::any::{Any, TypeId};
/// This trait is an extension trait to [`Any`], and adds methods to retrieve a `&dyn Any`
pub trait AsAny: Any {
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
/// Gets the type name of `self`
fn type_name(&self) -> TypeId;
}
impl<T: 'static> AsAny for T {
#[inline(always)]
fn as_any(&self) -> &dyn Any {
self
}
#[inline(always)]
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
#[inline(always)]
fn type_name(&self) -> TypeId {
TypeId::of::<T>()
}
}
/// This is a shim around `AaAny` to avoid some boilerplate code.
/// It is a separate trait because it is also implemented
/// on runtime polymorphic traits (which are `!Sized`).
pub trait Downcast: AsAny {
/// Returns `true` if the boxed type is the same as `T`.
///
/// Forward to the method defined on the type `Any`.
#[inline(always)]
fn is<T>(&self) -> bool
where
T: AsAny,
{
self.as_any().is::<T>()
}
/// Forward to the method defined on the type `Any`.
#[inline(always)]
fn downcast_ref<T>(&self) -> Option<&T>
where
T: AsAny,
{
self.as_any().downcast_ref()
}
/// Forward to the method defined on the type `Any`.
#[inline(always)]
fn downcast_mut<T>(&mut self) -> Option<&mut T>
where
T: AsAny,
{
self.as_any_mut().downcast_mut()
}
}
impl<T: ?Sized + AsAny> Downcast for T {}
上面的代码实现加入到了我的 Crate:boost-rs
在项目中使用:
examples/4_as_any.rs
use boost_rs::types::as_any::{AsAny, Downcast};
trait Custom: AsAny {
fn hello(&self) -> String;
}
struct Test {
age: i32,
}
impl Custom for Test {
fn hello(&self) -> String {
String::from("This is Test!")
}
}
fn main() {
let x: Box<dyn Custom> = Box::new(Test { age: 1 });
// Wrong:
// println!("age: {}", x.downcast_ref::<Test>().unwrap().age);
println!("age: {}", (*x).downcast_ref::<Test>().unwrap().age);
let y: &dyn Custom = &Test { age: 2 };
println!("age: {}", y.downcast_ref::<Test>().unwrap().age)
}
需要注意的是:
由于 Box 存在自动解引用的坑,如果是使用 Box 包装的类型,需要显式解引用 *y
!
否则调用的是 Box 实现的 Any Trait,会导致返回的是 None!
也可以使用全限定类型名继承,此时不会有问题:
trait Custom: boost_rs::types::as_any::AsAny
附录
参考文章:
Recommend
-
31
拼多多上市前接受多家纸媒的采访,我都会提到两个核心关键词:一个是社交链;一个是渠道下沉。
-
36
征集----跌了真的敢向下网格等金额不间断买入标的 - 征集线性的投资标的,如转债这样非线性的就不用推荐了。我主做转债,就是看重其非线性,向下的过程中,绝对是在最低点是在买入的,因为是网格的思想,也绝不会在最低点割肉,而线性的股票则不然,在最低点或相对...
-
35
来源 | 刘旷公众号 作者 | 刘旷 作为流媒体界的网红,爱奇艺一直备受关注。 北京时间2019年2月22日,爱奇艺公布...
-
33
当陈伟霆遇上《野狼disco》。
-
13
奈雪向上,喜茶向下? • 2020-12-15 14:36:13 来源:时趣研...
-
13
向上兼容,向下兼容,向前兼容,向后兼容 作者:青菜年糕汤 发现好久没写与数据系统有关的技术类文章了。 兼容,这是一个在实际工程中至关重要,但在学院教育中几乎不会被讨论到的话题。 在英文中,好像一般不怎么...
-
5
golang向上取整、向下取整和四舍五入 马谦的博客 · 2018-05-17 09:29:31 · 40710 次点击 · 预计阅读时间不到 1 分钟 · 不到1分钟之前 开始浏览 ...
-
13
2020年12月20日2020年12月20日 | by YoungTimes |
-
4
2021-02-19:给定一个二维数组matrix,一个人必须从左上角出发,最后到达右下角。沿途只可以向下或者向右走,沿途的数字都累加就是距离累加和。请问最小距离累加和是多少? 福哥答案2021-02-19: 自然智慧即可。 一般...
-
5
中国可转债的转股价向下修正条款 作者: 张志强 ...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK