我有这个代码(playground):
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
)。但是当我尝试编译它时出现错误:
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
方法如下:
fn send_embed<F: FnOnce(String) -> String>(&self, u64, &str, f: F)
-> Option<u64> where Self: Sized + Sync + Send;
然后编译成功但是:
- 为什么我们需要
Sized
这里?如果我们不能从特征对象使用此方法,这将违反多态性。 我们实际上不能使用来自
Arc<Messenger>
的这个方法然后: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
我完全被困在这里了。不知道如何在特征中使用泛型方法的多态性。有办法吗?
最佳答案
特质与特质
在 Rust 中,您可以使用 trait
定义一个接口(interface),包括:
- 关联类型,
- 相关常量,
- 相关职能。
你也可以使用特征:
- 作为通用参数的编译时界限
- 作为类型,在引用或指针后面。
但是...只有一些 trait 可以直接用作类型。那些符合条件的特性被标记为对象安全。
现在认为单个 trait
是不幸的存在关键字来定义全功能和对象安全的特征。
插曲:运行时分派(dispatch)是如何工作的?
当使用特征作为类型时:&Trait
, Box<Trait>
, Rc<Trait>
, ... 运行时实现使用由以下内容组成的胖指针:
- 数据指针,
- 虚拟指针。
方法调用通过指向虚拟表的虚拟指针分派(dispatch)。
对于这样的特征:
trait A {
fn one(&self) -> usize;
fn two(&self, other: usize) -> usize;
}
为 X
类型实现, 虚拟表看起来像 (<X as A>::one, <X as A>::two)
.
因此,运行时分派(dispatch)由以下人员执行:
- 选择合适的表成员,
- 使用数据指针和参数调用它。
这意味着<X as A>::two
看起来像:
fn x_as_a_two(this: *const (), other: usize) -> usize {
let x = unsafe { this as *const X as &X };
x.two(other)
}
为什么我不能使用任何特征作为类型?什么是对象安全?
这是技术限制。
有许多特性功能无法在运行时调度中实现:
- 关联类型,
- 相关常量,
- 相关的通用函数,
- 与
Self
相关的功能在签名中。 - ...也许其他...。
有两种方式表明这个问题:
- 早期:拒绝使用
trait
作为一种类型,如果它具有上述任何一种, - 迟到:拒绝在
trait
上使用上述任何一项作为一种类型。
目前,Rust 选择及早发出问题信号:不使用上述任何功能的特征称为对象安全,可以用作类型。
不对象安全的特征不能用作类型,并且会立即触发错误。
现在怎么办?
在您的情况下,只需将方法从编译时多态性切换到运行时多态性:
pub trait Messenger : Sync + Send {
fn send_embed(&self, u64, &str, f: &FnOnce(String) -> String)
-> Option<u64>;
}
有一点皱纹:FnOnce
需要搬出 f
它只是在这里借用的,所以你需要使用 FnMut
或 Fn
. FnMut
是下一个更通用的方法,所以:
pub trait Messenger : Sync + Send {
fn send_embed(&self, u64, &str, f: &FnMut(String) -> String)
-> Option<u64>;
}
这使得 Messenger
trait 对象安全,因此允许您使用 &Messenger
, Box<Messenger>
, ...
关于generics - 为什么特征内的泛型方法需要调整特征对象的大小?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42620022/