为什么特质内的通用方法需要特质对象的大小? [英] Why does a generic method inside a trait require trait object to be sized?
问题描述
我有以下代码(游乐场) :
use std::sync::Arc;
pub trait Messenger : Sync + Send {
fn send_embed<F: FnOnce(String) -> String>(&self, u64, &str, f: F)
-> Option<u64> where Self: Sync + Send;
}
struct MyMessenger {
prefix: String,
}
impl MyMessenger {
fn new(s: &str) -> MyMessenger {
MyMessenger { prefix: s.to_owned(), }
}
}
impl Messenger for MyMessenger {
fn send_embed<F: FnOnce(String) -> String>(&self, channel_id: u64, text: &str, f: F) -> Option<u64> {
println!("Trying to send embed: chid={}, text=\"{}\"", channel_id, text);
None
}
}
struct Bot {
messenger: Arc<Messenger>,
}
impl Bot {
fn new() -> Bot {
Bot {
messenger: Arc::new(MyMessenger::new("HELLO")),
}
}
}
fn main() {
let b = Bot::new();
}
我想制作一个多态对象(特征Messenger
,多态实现之一是MyMessenger
).但是当我尝试编译它时,我出现了一个错误:
I wanted to make a polymorphic object (trait Messenger
and one of polymorphic implementations is MyMessenger
). But when I try to compile it I have an error:
error[E0038]: the trait `Messenger` cannot be made into an object
--> <anon>:25:5
|
25 | messenger: Arc<Messenger>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Messenger` cannot be made into an object
|
= note: method `send_embed` has generic type parameters
我发现在这种情况下我必须要求Sized
,但这不能解决问题.如果我将send_embed
方法更改为以下内容:
I have found that I must require Sized
in this case, but this does not solve it. If I change my send_embed
method to the following:
fn send_embed<F: FnOnce(String) -> String>(&self, u64, &str, f: F)
-> Option<u64> where Self: Sized + Sync + Send;
然后它可以成功编译,但是:
Then it compiles successfully but:
- 为什么在这里需要
Sized
?如果我们无法从特征对象中使用此方法,则会违反多态性. -
我们实际上不能从
Arc<Messenger>
开始使用此方法:
- Why do we need
Sized
here? This violates polymorphism if we can not use this method from a trait object. We actually can't use this method from
Arc<Messenger>
then:
fn main() {
let b = Bot::new();
b.messenger.send_embed(0u64, "ABRACADABRA", |s| s);
}
赠予:
error[E0277]: the trait bound `Messenger + 'static: std::marker::Sized` is not satisfied
--> <anon>:37:17
|
37 | b.messenger.send_embed(0u64, "ABRACADABRA", |s| s);
| ^^^^^^^^^^ the trait `std::marker::Sized` is not implemented for `Messenger + 'static`
|
= note: `Messenger + 'static` does not have a constant size known at compile-time
我完全被困在这里.不知道如何在特征中将多态与泛型方法结合使用.有办法吗?
I am totally stuck here. No idea how to use polymorphism with generic method in a trait. Is there a way?
推荐答案
动态分派(即通过trait对象调用方法)通过vtable进行调用(即使用函数指针),因为您对编译不了解时间是哪个功能.
Dynamic dispatch (i.e. calling methods through trait objects) works by calling through a vtable, (i.e. using a function pointer), since you don't know at compile time which function it will be.
但是,如果您的函数是通用函数,则需要为每个实际使用的F
实例进行不同的编译(单态化).这意味着对于调用它的每种不同的闭包类型,您将拥有send_embed
的不同副本.每个闭包都是不同的类型.
But if your function is generic, it needs to be compiled differently (monomorphised) for every instance of F
which is actually used. Which means you'll have a different copy of send_embed
for every different closure type it's called with. Every closure is a different type.
这两个模型是不兼容的:您不能拥有适用于不同类型的函数指针.
These two models are incompatible: you can't have a function pointer which works with different types.
但是,您可以将方法更改为也使用trait对象,而不是使用编译时通用的方法:
However, you can change the method to use a trait object as well instead of being compile-time generic:
pub trait Messenger : Sync + Send {
fn send_embed(&self, u64, &str, f: &Fn(String) -> String)
-> Option<u64> where Self: Sync + Send;
}
(游乐场)
现在可以接受特征对象引用,而不是每个可能为Fn(String) -> String
的类型都使用不同的send_embed
. (您也可以使用Box<Fn()>
或类似名称).您必须使用Fn
或FnMut
而不是FnOnce
,因为后者按值取self
,即,它也不是对象安全的(调用者不知道传递什么大小作为闭包的self
参数).
Instead of a different send_embed
for every type which can be Fn(String) -> String
, it now accepts a trait object reference. (You could also use a Box<Fn()>
or similar). You do have to use Fn
or FnMut
and not FnOnce
, since the latter takes self
by value, i.e. it's also not object safe (the caller doesn't know what size to pass in as the closure's self
parameter).
您仍然可以使用闭包/lambda函数调用send_embed
,但这只需要通过引用进行,就像这样:
You can still call send_embed
with a closure/lambda function, but it just needs to be by reference, like this:
self.messenger.send_embed(0, "abc", &|x| x);
我已经更新了操场,包括了一个使用引用的闭包直接调用send_embed
的示例,以及通过Bot
上的通用包装程序进行间接路由的示例.
I've updated the playground to include an example of calling send_embed
directly with a referenced closure, as well as the indirect route through a generic wrapper on Bot
.
这篇关于为什么特质内的通用方法需要特质对象的大小?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!