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

标签 generics rust

我有这个代码(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;

然后编译成功但是:

  1. 为什么我们需要 Sized这里?如果我们不能从特征对象使用此方法,这将违反多态性。
  2. 我们实际上不能使用来自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它只是在这里借用的,所以你需要使用 FnMutFn . 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/

相关文章:

c# - 从变量实例化泛型类型

c# - 为什么通用类型定义实现的接口(interface)会丢失类型信息?

rust - 将结果转换为选项的简洁方法是什么?

error-handling - 如何将 io::Result 转换为 anyhow::Result?

java - 在下面的示例中, "List<? extends T>"是必要的吗?或者 "List<T>"会做同样的事情吗?

泛型类型的 C# 方法解析

java - 为什么调用构造函数时不需要泛型类型?

rust - 在遍历 Vec 的元素时使用 powi

Rust 中的 Unix 选择系统调用

rust - 如何筛选~[T]