Rust中的通用类型闭包


在这篇简短的文章中,我将讨论Rust中的模式,该模式允许您“保存”供以后使用通过通用方法传递的类型。 这种模式可以在Rust库的源库中找到,有时我也会在我的项目中使用它。 我无法在网络上找到有关他的出版物,所以给他起了我的名字:“通用类型闭包”,在本文中,我想告诉您它的含义,使用原因以及使用方法。


问题


在Rust中,开发的静态类型系统及其静态功能足以满足80%的情况。 但是,当您要将不同类型的对象存储在同一位置时,碰巧需要动态键入。 字符类型对象在这里很抢手:它们擦除对象的真实类型,将它们简化为由类型定义的某个通用接口,然后您可以像对待相同类型的对象一样对这些对象进行操作。


在其余的一半情况下,这种方法效果很好。 但是,如果在使用对象时我们仍然需要恢复已擦除对象的类型,该怎么办? 例如,如果我们的对象的行为是由不能用作type-object的类型确定的 。 对于具有关联类型的特征,这是一种常见情况。 在这种情况下该怎么办?


解决方案


对于所有'static类型”(即,不包含非静态链接的类型),Rust实现Any类型,这允许将dyn Any类型对象转换为对原始对象类型的引用:


 let value = "test".to_string(); let value_any = &value as &dyn Any; //       String.  //   -      . if let Some(as_string) = value_any.downcast_ref::<String>() { println!("String: {}", as_string); } else { println!("Unknown type"); } 


Box也有一个为此目的的downcast方法。


此解决方案适用于在工作地点知道源类型的情况。 但是,如果不是这样呢? 如果调用代码只是不知道使用对象的源类型,该怎么办? 然后,我们需要以某种方式记住原始类型,将其放在定义的位置,并与dyn Any类型对象一起保存,以便稍后将后者转换到正确位置的原始类型。


Rust中的通用类型可以视为类型变量,在调用时可以将一个或另一个类型值传递到其中。 但是在Rust中,没有办法记住这种类型以便在其他地方进一步使用。 但是,有一种方法可以记住使用此类型以及该类型的所有功能。 这就是“关闭通用类型”模式的想法:使用类型的代码以闭包的形式执行,该闭包以普通函数的形式存储,因为除通用类型外,它不使用环境的任何对象。


实作


让我们看一个实现示例。 假设我们要创建一个表示图形对象层次结构的递归树,其中每个节点可以是带有子节点的图形基元,也可以是一个组件-一个单独的图形对象树:


 enum Node { Prim(Primitive), Comp(Component), } struct Primitive { shape: Shape, children: Vec<Node>, } struct Component { node: Box<Node>, } enum Shape { Rectangle, Circle, } 

由于Component结构本身是在Node使用的,因此必须在Component结构中打包Node


现在假设我们的树只是它应该与之关联的某种模型的表示。 此外,每个组件都有自己的模型:


 struct Primitive<Model> { shape: Shape, children: Vec<Node<Model>>, } struct Component<Model> { node: Box<Node<Model>>, model: Model, //   Model } 

我们可以这样写:


 enum Node<Model> { Prim(Primitive<Model>), Comp(Component<Model>), } 

但是此代码无法按照我们的需要工作。 因为组件必须具有自己的模型,而不是包含组件的父元素的模型。 也就是说,我们需要:


 enum Node<Model> { Prim(Primitive<Model>), Comp(Component), } struct Primitive<Model> { shape: Shape, children: Vec<Node<Model>>, _model: PhantomData<Model>, //   Model } struct Component { node: Box<dyn Any>, model: Box<dyn Any>, } impl Component { fn new<Model: 'static>(node: Node<Model>, model: Model) -> Self { Self { node: Box::new(node), model: Box::new(model), } } } 


我们已经将模型的特定类型的指示移到了new方法上,并且在组件本身中,我们已经存储了具有擦除类型的模型和子树。


现在添加use_model方法,该方法将使用模型,但不会通过其类型进行参数化:


 struct Component { node: Box<dyn Any>, model: Box<dyn Any>, use_model_closure: fn(&Component), } impl Component { fn new<Model: 'static>(node: Node<Model>, model: Model) -> Self { let use_model_closure = |comp: &Component| { comp.model.downcast_ref::<Model>().unwrap(); }; Self { node: Box::new(node), model: Box::new(model), use_model_closure, } } fn use_model(&self) { (self.use_model_closure)(self); } } 


请注意,在组件中,我们存储了一个指向在new方法中使用定义闭包的语法创建的函数的指针。 但是它应该从外部捕获的全部是Model类型,因此我们被迫通过参数将指向组件本身的链接传递给该函数。


似乎可以使用内部函数来代替闭包,但是此类代码无法编译。 因为Rust仅在可见性方面与常规顶级函数不同,所以Rust内部函数无法从外部捕获通用类型

use_model可以在实际的Model类型未知的上下文中使用use_model方法。 例如,在包含许多具有不同模型的不同组件的递归树遍历中。


另类


如果可以将组件的接口转换为允许创建类型对象的类型,则最好这样做,而应使用组件本身对其类型对象进行操作:


 enum Node<Model> { Prim(Primitive<Model>), Comp(Box<dyn ComponentApi>), } struct Component<Model> { node: Node<Model>, model: Model, } impl<Model> Component<Model> { fn new(node: Node<Model>, model: Model) -> Self { Self { node, model, } } } trait ComponentApi { fn use_model(&self); } impl<Model> ComponentApi for Component<Model> { fn use_model(&self) { &self.model; } } 


结论


事实证明,Rust中的闭包不仅可以捕获环境对象,还可以捕获类型。 但是,它们可以解释为普通功能。 如果字符类型不适用,当您需要统一处理不同类型而不丢失有关它们的信息时,此属性将非常有用。


我希望本文能帮助您使用Rust。 在评论中分享您的想法。

Source: https://habr.com/ru/post/zh-CN454310/


All Articles