这段代码可以在没有泛型的情况下编写吗?

Chr*_*oph 2 generics rust

我有这个结构:

#[deriving(Clone)]
pub struct MiddlewareStack {
    handlers: Vec<Box<Middleware + Send>>
}
Run Code Online (Sandbox Code Playgroud)

然后我有代码添加处理程序 handlers

pub fn utilize<T: Middleware>(&mut self, handler: T){
    self.middleware_stack.add(handler);
}
Run Code Online (Sandbox Code Playgroud)

这工作正常,但我想知道,为什么必须使用泛型?

所以我尝试了这个:

pub fn utilize(&mut self, handler: Middleware){
    self.middleware_stack.add(handler);
}
Run Code Online (Sandbox Code Playgroud)

但这让我有这个错误:

error: reference to trait `Middleware` where a type is expected; try `Box<Middleware>` or `&Middleware`
       pub fn utilize(&mut self, handler: Middleware){
Run Code Online (Sandbox Code Playgroud)

那好吧.特征不能直接用作参数(因为它们会被删除?).但那么为什么它们作为通用类型参数合法?

所以,我继续尝试:

pub fn utilize(&mut self, handler: Box<Middleware + Send>){
    self.middleware_stack.add(handler);
}
Run Code Online (Sandbox Code Playgroud)

但这让我有以下错误:

error: failed to find an implementation of trait middleware::Middleware for Box<middleware::Middleware:Send>
       self.middleware_stack.add(handler);
Run Code Online (Sandbox Code Playgroud)

所以,我想知道:那些代码真的必须使用泛型吗?不是,有一个特殊的原因,我不希望它不使用泛型.更多的是我想要理解为什么它必须使用泛型,因为来自诸如C#的Java之类的语言它可能只是一个非泛型方法,它使用接口作为参数,应该粗略地转换为Rust中的特征.

跟进弗拉基米尔的优秀答案

你试图在这个函数中传递一个特征对象.但是特征对象没有实现相应的特征,也就是说,它们的类型不满足它们各自的特征边界,除非这些特征明确地在它们上实现

我想,这对我来说很奇怪.我希望能用self.middleware_stack.add(handler)一个盒装的Middleware给出add看起来像这样:

pub fn add<T: Middleware> (&mut self, handler: T) {
    self.handlers.push(box handler);
}
Run Code Online (Sandbox Code Playgroud)

但是好的结果:Middleware是不满意a Box<Middleware>并且在第二个想法这实际上是有道理的.否则我最终会在上面的代码中以双拳结束.

如果我改变利用:

pub fn utilize(&mut self, handler: Box<Middleware + Send>){
    self.middleware_stack.add(handler);
}
Run Code Online (Sandbox Code Playgroud)

add

pub fn add (&mut self, handler: Box<Middleware+Send>) {
    self.handlers.push(handler);
}
Run Code Online (Sandbox Code Playgroud)

它按预期工作.请注意,它们不在同一模块中,因此封装.但它暗示在调用者站点上我必须将代码更改为utilize(box some_middleware).

通过通用实现,我能够调用box我的图层的最底层

pub fn add<T: Middleware> (&mut self, handler: T) {
    self.handlers.push(box handler);
}
Run Code Online (Sandbox Code Playgroud)

但是对于非通用实现,我必须在调用者站点上打包,否则我会遇到:

error: reference to trait `Middleware` where a type is expected; try `Box<Middleware>` or `&Middleware`
Run Code Online (Sandbox Code Playgroud)

让我们面对现实:我永远不会有Middleware一个简单的参数.我总是需要或者Box<Middleware>&Middleware这意味着我必须做拳击在这个过程中,而使用泛型,我可以做拳击某种方式的道路.

我想我还没有完全掌握为什么会这样.因为如果编译器翻译

pub fn add<T: Middleware> (&mut self, handler: T) {
    self.handlers.push(box handler);
}
Run Code Online (Sandbox Code Playgroud)

成:

pub fn add (&mut self, handler: Middleware) {
    self.handlers.push(box handler);
}
Run Code Online (Sandbox Code Playgroud)

无论如何,在某些时候.

为什么不允许使用未装箱版本Middleware作为简单参数,如果这或多或少是编译器在幕后做什么呢?

Vla*_*eev 5

Rust目前提供了两种编写多态代码的方法:泛型和特征对象.

泛型以类型参数的形式存在.也就是说,函数具有由调用者选择的附加参数.然后,编译器生成函数的相应单态版本,其中所有类型参数都替换为具体类型:

fn add<T: Add<T, Output=T>>(a: T, b: T) -> T {
    a + b
}

// when used like this:
let (a, b) = (1, 2);
let c = add(a, b);

// roughly the following code will be generated:
fn add(a: i32, b: i32) -> i32 {
    a + b
}
Run Code Online (Sandbox Code Playgroud)

你知道,这是非常有效的:单态化导致了最快的代码,它不依赖于任何类型的间接.在任何时候,编译器都知道应该调用哪些函数以及确切地使用哪些类型.

另一方面,特征对象允许"擦除"给定数据的实际类型,只留下这条数据实现的特征列表.因为编译器不知道所使用的实际类型,所以它不知道生成使用该类型对象的代码所需的大小,因此应始终通过某个指针访问特征对象.当您需要一些异构集合时,通常会使用特征对象,例如,可以包含不同类型项的向量,前提是它们都具有相同的特征集:

fn show_all(v: &[&Display]) {
    for (i, item) in v.iter().enumerate() {
        println!("v[{}] = {}", i, item);
    }
}

let a = 10;
let b = "abcd";
let c = 0.9f64;
show_all(&[&a, &b, &c]);
Run Code Online (Sandbox Code Playgroud)

请注意,向量包含不同实际类型的元素,但它们都满足Display特征.

但是,与泛型不同,特征对象会对性能产生影响.因为编译器不知道它们的具体类型,所以应该使用vtable来查找要对它们执行的方法.因为trait对象应始终通过指针访问,所以您只能将它们保留在堆栈上或将它们装箱以将它们存储在结构中.例如,不可能将"裸"特征对象保存到结构的字段中.

并非所有特征都可以产生特征对象,或者,换句话说,并非所有特征都对特征对象有用.例如,Self在签名中具有类型的特征方法不能用于特征对象.原因应该是显而易见的:这些方法要求在调用站点处知道具体类型的实现者,而特征对象不是这种情况.

备注:实际上可以在结构中存储裸特征对象,尽管有一些限制.例如,您只能将裸特征对象存储为结构的最后一个字段,并且此类结构只能通过指针访问,因为它也会变为未分级.您可以在此处阅读有关未规划类型(或动态大小类型,这些是同义词)的更多信息.

更多的是我想要理解为什么它必须使用泛型,因为来自诸如C#的Java之类的语言它可能只是一个非泛型方法,它使用接口作为参数,应该粗略地转换为Rust中的特征.

从上述所有内容可以看出,在绝大多数情况下,泛型更有用,更有效.因为这个Rust 促进了泛型而不是特征对象的使用.因此,当您需要编写泛型函数时,您需要从泛型开始并仅在您真正需要时才转向特征对象.在这方面,Rust与Java或C#不同.

但是,您的具体问题似乎在于您正在调用middleware_stack.add()方法,根据错误消息,该方法似乎是通用的.它应该如下所示:

fn add<T: Middleware+Send>(&mut self, handler: T) { ... }
Run Code Online (Sandbox Code Playgroud)

(与您的通用版本完全一样).这是你的错误的原因:你试图在这个函数内传递特征对象.但是特征对象没有实现相应的特征,也就是说,它们的类型不满足它们各自的特征边界,除非这些特征明确地在它们上实现:

impl Middleware for Box<Middleware> { ... }
Run Code Online (Sandbox Code Playgroud)

似乎情况并非如此,Middleware并未实施Box<Middleware>.因此你不能add<T: Middleware+Send>()在它上面调用函数.

如果utilize()在与MiddlewareStack结构相同的模块中定义方法,则可以直接访问其字段:

pub fn utilize(&mut self, handler: Box<Middleware+Send>){
    self.middleware_stack.handlers.push(handler);
}
Run Code Online (Sandbox Code Playgroud)

这将起作用,但前提是此方法在与MiddlewareStack结构相同的模块中定义,因为handlers字段是私有的.

回答后续行动

我不确定你为什么决定编译器翻译

pub fn add<T: Middleware> (&mut self, handler: T) {
    self.handlers.push(box handler);
}
Run Code Online (Sandbox Code Playgroud)

成:

pub fn add (&mut self, handler: Middleware) {
    self.handlers.push(box handler);
}
Run Code Online (Sandbox Code Playgroud)

这不是它的工作原理.当使用特定类型调用时,上面的通用版本是单形的,这就是我的帖子中的第一个示例所示.例如,如果您有impl Middleware for SomeHandler和您调用self.add(SomeHandler { ... }),编译器将生成特定版本的add()方法:

pub fn add(&mut self, handler: SomeHandler) {
    self.handlers.push(box handler);
}
Run Code Online (Sandbox Code Playgroud)

它的工作原理应该非常简单.

回答对其他答案的评论的最新后续行动

最后的跟进.在上面的例子中,你会赞成非泛型实现的泛型实现,对吧?基本上是因为你不想将拳击"泄漏"到呼叫者,对吗?至少对我来说这将是最烦人的事情.我不想使用Box作为参数并强制调用者调用util(box some_middleware).这种通用实现更加美观,不会一直强制执行拳击.那会是核心动机吗?

事实上,这只是一个动机.但我认为最重要的一点是仿制药更有效率.我在上面说过:泛型函数的单态化允许静态分派,即编译器确切地知道函数的哪个变体被调用,并且可以基于这些知识应用优化,例如内联.对特征对象进行内联是不可能的,因为对特征对象方法的所有调用都应该通过该对象的虚拟表.

你也可以通过@dbaupp 阅读这个很好的解释(尽管这是一个有些不同的问题的答案).只需更换Go,Java/C#您将得到大致相同的东西.