简体   繁体   English

为什么特征中的泛型方法需要调整特征对象的大小?

[英]Why does a generic method inside a trait require trait object to be sized?

I have this code ( 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();
}

I wanted to make a polymorphic object (trait Messenger and one of polymorphic implementations is MyMessenger ). 我想制作一个多态对象(trait Messenger和多态实现之一是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

I have found that I must require Sized in this case, but this does not solve it. 我发现在这种情况下我必须要求Sized ,但这并没有解决它。 If I change my send_embed method to the following: 如果我将send_embed方法更改为以下内容:

fn send_embed<F: FnOnce(String) -> String>(&self, u64, &str, f: F)
    -> Option<u64> where Self: Sized + Sync + Send;

Then it compiles successfully but: 然后它成功编译但是:

  1. Why do we need Sized here? 为什么我们这里需要Sized This violates polymorphism if we can not use this method from a trait object. 如果我们不能从特征对象使用此方法,则这会违反多态性。
  2. We actually can't use this method from Arc<Messenger> then: 我们实际上无法使用Arc<Messenger>此方法:

     fn main() { let b = Bot::new(); b.messenger.send_embed(0u64, "ABRACADABRA", |s| s); } 

    Gives: 得到:

     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? 有办法吗?

Traits and Traits 特征和特征

In Rust, you can use trait to define an interface comprised of: 在Rust中,您可以使用trait定义包含以下内容的接口:

  • associated types, 相关类型,
  • associated constants, 相关常数,
  • associated functions. 相关功能。

and you can use traits either: 你可以使用以下特征:

  • as compile-time bounds for generic parameters 作为泛型参数的编译时绑定
  • as types, behind references or pointers. 作为类型,在引用或指针后面。

However... only some traits can be used directly as types. 但是......只有一些特征可以直接用作类型。 Those traits that do are labeled Object Safe . 那些特征标记为对象安全

It is now considered unfortunate that a single trait keyword exists to define both full-featured and object-safe traits. 现在认为存在单个trait关键字以定义全功能和对象安全特征是不幸的。


Interlude: How does run-time dispatch work? 插曲:运行时调度如何工作?

When using a trait as a type: &Trait , Box<Trait> , Rc<Trait> , ... the run-time implementation uses a fat pointer composed of: 当使用特征作为类型时: &TraitBox<Trait>Rc<Trait> ,...运行时实现使用由以下组成的胖指针:

  • the data pointer, 数据指针,
  • the virtual pointer. 虚拟指针。

Method calls are dispatched through the virtual pointer to a virtual table. 方法调用通过虚拟指针分派到虚拟表。

For a trait like: 对于如下特征:

trait A {
    fn one(&self) -> usize;
    fn two(&self, other: usize) -> usize;
}

implemented for type X , the virtual table will look like (<X as A>::one, <X as A>::two) . 对于类型X实现,虚拟表看起来像(<X as A>::one, <X as A>::two)

The run-time dispatch is thus performed by: 因此,运行时调度由以下方式执行:

  • picking the right member of the table, 选择正确的桌子成员,
  • calling it with the data pointer and arguments. 用数据指针和参数调用它。

This means that <X as A>::two looks like: 这意味着<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)
}

Why cannot I use any trait as a type? 为什么我不能使用任何特质作为一种类型? What's Object Safe? 什么是对象安全?

It's a technical limitation. 这是一个技术限制。

There are a number of traits capabilities that cannot be implemented for run-time dispatches: 有许多特征功能无法为运行时调度实现:

  • associated types, 相关类型,
  • associated constants, 相关常数,
  • associated generic functions, 相关的通用函数,
  • associated functions with Self in the signature. 签名中与Self相关的函数。
  • ... maybe others ... . ......也许是其他人......

There are two ways to signal this issue: 有两种方法可以表明这个问题:

  • early: refuse to use a trait as a type if it has any of the above, 提前:如果上述任何trait ,拒绝使用trait作为类型,
  • late: refuse to use any of the above on a trait as a type. 迟到:拒绝在trait上使用上述任何一种。

For now, Rust chooses to signal the issue early on: traits that do not use any of the above features are call Object Safe and can be used as types. 目前,Rust选择在早期发出问题信号:不使用上述任何功能的特性称为Object Safe ,可用作类型。

Traits that are not Object Safe cannot be used as types, and an error is immediately triggered. 不是对象安全的特征不能用作类型,并且会立即触发错误。


Now what? 怎么办?

In your case, simply switch from compile-time polymorphism to run-time polymorphism for the method: 在您的情况下,只需从编译时多态切换到该方法的运行时多态:

pub trait Messenger : Sync + Send {
    fn send_embed(&self, u64, &str, f: &FnOnce(String) -> String)
        -> Option<u64>;
}

There is a little wrinkle: FnOnce requires moving out of the f and it's only borrowed here, so instead you need to use FnMut or Fn . 有一点皱纹: FnOnce需要移出f而且它只是在这里借来的,所以你需要使用FnMutFn FnMut is next more generic method, so: FnMut是下一个更通用的方法,因此:

pub trait Messenger : Sync + Send {
    fn send_embed(&self, u64, &str, f: &FnMut(String) -> String)
        -> Option<u64>;
}

This makes the Messenger trait Object Safe and therefore allows you to use a &Messenger , Box<Messenger> , ... 这使得Messenger特征对象安全,因此允许您使用&MessengerBox<Messenger> ,...

Dynamic dispatch (ie calling methods through trait objects) works by calling through a vtable, (ie using a function pointer), since you don't know at compile time which function it will be. 动态调度(即通过特征对象调用方法)通过调用vtable(即使用函数指针)来工作,因为在编译时你不知道它将是哪个函数。

But if your function is generic, it needs to be compiled differently (monomorphised) for every instance of F which is actually used. 但是如果你的函数是通用的,那么它需要针对实际使用的每个F实例进行不同的编译(单态)。 Which means you'll have a different copy of send_embed for every different closure type it's called with. 这意味着对于调用它的每个不同的闭包类型,您将拥有不同的send_embed副本。 Every closure is a different type. 每个闭合都是不同的类型。

These two models are incompatible: you can't have a function pointer which works with different types. 这两个模型是不兼容的:你不能有一个适用于不同类型的函数指针。

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;
}

( Playground ) 游乐场

Instead of a different send_embed for every type which can be Fn(String) -> String , it now accepts a trait object reference. 现在,它接受一个特征对象引用,而不是对于每个可以是Fn(String) -> String类型使用不同的send_embed (You could also use a Box<Fn()> or similar). (您也可以使用Box<Fn()>或类似的东西)。 You do have to use Fn or FnMut and not FnOnce , since the latter takes self by value, ie it's also not object safe (the caller doesn't know what size to pass in as the closure's self parameter). 你必须使用FnFnMut而不是FnOnce ,因为后者采用self取值,即它也不是对象安全的(调用者不知道传递的大小与闭包的self参数一样)。

You can still call send_embed with a closure/lambda function, but it just needs to be by reference, like this: 您仍然可以使用closure / lambda函数调用send_embed ,但它只需要通过引用,如下所示:

self.messenger.send_embed(0, "abc", &|x| x);

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 . 我已经更新了操场,以包含一个直接使用引用的闭包调用send_embed的示例,以及通过Bot上的通用包装器的间接路由。

A generic method cannot be made object-safe , because you can't implement a vtable with it. 通用方法不能成为对象安全的 ,因为您无法使用它实现vtable。 @ChrisEmerson's answer explained in detail why. @ ChrisEmerson的回答详细解释了为什么。

In your case, you could make send_embed object-trait, by making f take a trait-object instead of generic parameter. 在你的情况下,你可以通过使f取一个特征对象而不是泛型参数来制作send_embed对象特征。 If your function accepts an f: F where F: Fn(X) -> Y , you could make it accept f: &Fn(X) -> Y , similarly for FnMut f: &mut FnMut(X) -> Y . 如果你的函数接受f: F where F: Fn(X) -> Y ,你可以接受f: &Fn(X) -> Y ,类似于FnMut f: &mut FnMut(X) -> Y FnOnce is more tricky since Rust doesn't support moving unsized types, but you could try to Box it: 由于Rust不支持移动未经过类型化的类型,因此FnOnce更加棘手,但您可以尝试将其装箱:

//           ↓ no generic          ↓~~~~~~~~~~~~~~~~~~~~~~~~~~~~ box the closure
fn send_embed(&self, u64, &str, f: Box<FnOnce(String) -> String>) -> Option<u64> 
    where Self: Sync + Send
{
    f("hello".to_string());
    None
}

b.messenger.send_embed(1, "234", Box::new(|a| a));
// note: does not work.

However, as of Rust 1.17.0 you cannot box an FnOnce and call it , you have to use FnBox : 但是,从Rust 1.17.0开始, 你无法装入FnOnce并调用它 ,你必须使用FnBox

#![feature(fnbox)]
use std::boxed::FnBox;

//                                     ↓~~~~
fn send_embed(&self, u64, &str, f: Box<FnBox(String) -> String>) -> Option<u64> 
    where Self: Sync + Send 
{
    f("hello".to_string());
    None
}

b.messenger.send_embed(1, "234", Box::new(|a| a));

If you don't want to use unstable feature, you could use the crate boxfnonce as a workaround: 如果您不想使用不稳定的功能,可以使用crate boxfnonce作为解决方法:

extern crate boxfnonce;
use boxfnonce::BoxFnOnce;

fn send_embed(&self, u64, &str, f: BoxFnOnce<(String,), String>) -> Option<u64> 
    where Self: Sync + Send 
{
    f.call("hello".to_string());
    None
}

b.messenger.send_embed(1, "234", BoxFnOnce::from(|a| a));

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM