为什么 trait 中的泛型方法需要调整 trait 对象的大小? [英] Why does a generic method inside a trait require trait object to be sized?

查看:36
本文介绍了为什么 trait 中的泛型方法需要调整 trait 对象的大小?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有这个代码(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).但是当我尝试编译它时,我有一个错误:

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;

然后它编译成功但是:

  1. 为什么我们需要 Sized 在这里?如果我们不能从 trait 对象中使用这个方法,这就违反了多态性.
  2. 我们实际上不能从 Arc 使用这个方法然后:

  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) -> 的每种类型,而不是不同的 send_embedString,它现在接受一个 trait 对象引用.(您也可以使用 Box 或类似的).您必须使用 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);

我已经更新了 Playground 以包含一个使用引用的闭包直接调用 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.

这篇关于为什么 trait 中的泛型方法需要调整 trait 对象的大小?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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