为什么特质内的通用方法需要特质对象的大小? [英] Why does a generic method inside a trait require trait object to be sized?

查看:68
本文介绍了为什么特质内的通用方法需要特质对象的大小?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下代码(游乐场) :

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:

  1. 为什么在这里需要Sized?如果我们无法从特征对象中使用此方法,则会违反多态性.
  2. 我们实际上不能从Arc<Messenger>开始使用此方法:

  1. Why do we need Sized here? 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:

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()>或类似名称).您必须使用FnFnMut而不是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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆