当前位置: 首页 > 知识库问答 >
问题:

当以“Self:Sized”为边界时,为什么不能调用trait对象上的函数?

周正真
2023-03-14

我有以下代码

trait Bar {
    fn baz(&self, arg: impl AsRef<str>)
    where
        Self: Sized;
}

struct Foo;

impl Bar for Foo {
    fn baz(&self, arg: impl AsRef<str>) {}
}

fn main() {
    let boxed: Box<dyn Bar> = Box::new(Foo);
    boxed.baz();
}

游戏场

导致此错误的原因:

error: the `baz` method cannot be invoked on a trait object
  --> src/main.rs:15:11
   |
15 |     boxed.baz();
   |           ^^^

为什么这是不可能的?当我删除Self:Sized绑定时,它就可以工作了,但是我不能使用泛型使函数对调用者更舒适。

这不是重复为什么trait中的泛型方法需要调整trait对象的大小?这会询问为什么不能从trait对象调用baz。我不是在问为什么需要边界;这一点已经讨论过了。

共有2个答案

爱繁
2023-03-14

绑定使方法不是对象安全的。对象不安全的特性不能用作类型。

Self作为参数、返回Self或以其他方式要求Self:Sized的方法不是对象安全的。这是因为trait对象上的方法是通过动态分派调用的,在编译时无法知道trait实现的大小霍尔

引用官方文件:

只有对象安全的特征才能被制作成特征对象。如果这两个都是true,则trait是对象安全的:

  • 特性不需要Self: Size
  • 它的所有方法都是对象安全的

那么,是什么使方法成为对象安全的呢?每个方法必须要求Self: Size或以下所有内容:

  • 必须没有任何类型参数
  • 不得使用Self

另见:

  • 为什么trait中的泛型方法需要调整trait对象的大小
胡越泽
2023-03-14

因为Rust的泛型系统是通过单纯化工作的。

例如,在Java中,泛型函数中的类型参数转变为Object类型的变量,并根据需要进行转换。像这样的语言中的泛型只是作为一种工具来帮助验证代码中类型的正确性。

Rust和C等语言对泛型使用单构词。对于调用泛型函数的每个类型参数组合,将生成专用机器代码,该代码使用这些类型参数组合运行该函数。该函数是单态的。这允许将数据存储在适当的位置,消除了强制转换的成本,并允许通用代码调用该类型参数的“静态”函数。

那么,为什么你不能在特质对象上这样做呢?

许多语言中的Trait对象(包括Rust)都是使用vtable实现的。当您有某种类型的指向trait对象的指针(raw、reference、Box、reference counter等)时,它包含两个指针:指向数据的指针和指向vtable条目的指针。vtable条目是存储在不可变内存区域中的函数指针的集合,它指向该特性方法的实现。因此,当您对trait对象调用方法时,它会在vtable中查找实现的函数指针,然后间接跳转到该指针。

不幸的是,如果Rust编译器在编译时不知道实现函数的代码,它就不能将函数进行单序列化,这就是在trait对象上调用方法时的情况。因此,您不能在trait对象上调用泛型函数(嗯,泛型超过类型)。

-编辑-

听起来像是在问为什么: Size限制是必要的。

:Size使得trait不能用作trait对象。我想可能有几个选择。Rust可能会隐式地使任何具有泛型函数的特征不具有对象安全性。Rust还可以隐式地阻止对trait对象调用泛型函数。

然而,Rust试图明确编译器正在做的事情,而这些隐式方法将与之相反。对于初学者来说,在trait对象上尝试调用泛型函数,但却无法编译,这难道不会让人感到困惑吗?

相反,Rust允许您显式地使整个trait对象不安全

特征:大小{

或显式地使某些功能仅在静态分派时可用

fn-foo

 类似资料:
  • 我不得不问:从逻辑上讲,为什么地址边界本身在什么上可分很重要?使用地址上的整数将一组内存分配给的调优有什么问题? 我知道指针算术是如何工作的,但我无法计算边界的重要性······

  • 我有这个(看起来)无辜的代码(这里简化成这个JUnit测试用例): 问题在于行:Eclipse编译器在这里没有发现问题(我也是),很高兴编译并运行测试并成功。 但是,当我在命令行上编译它时(在这种情况下是Maven 3,Oracle JDK8,但也不能直接与javac一起使用),会抛出一个编译错误: 我认为从返回类型(< code>Map 这是JDK的错误还是Eclipse编译器的错误还是别的什么

  • 我正在使用forEach循环一个nodeList。我的代码如下 此代码引发错误为 未捕获的TypeError:Array.Foreach不是函数 然而,一些较旧的浏览器还没有实现nodelist.foreach()和array.from()。但是这些限制可以通过使用array.prototype.foreach()来规避(本文档中有更多内容)。 参考:MDN

  • 我使用从iPython笔记本下载CSV时建议的代码动态构建javascript代码,并在从jupyter笔记本调用时使用python中的javascript()将其传递给浏览器。代码工作得很好。如果我在python函数中嵌入相同的代码,并从同一个jupyter笔记本调用python函数,那么python中的调用Javascript()将不再有效。如何使可重用功能正常工作? 我正在Windows 1

  • 问题内容: 我在Java中注意到,您可以将具有object…作为参数的函数,然后method将任意数量的对象作为参数并将其视为数组。这个叫什么?我一直在尝试搜索它,但似乎…被搜索引擎忽略。我似乎记得C中的printf做同样的事情。 谢谢。 问题答案: 叫做varargs

  • 问题内容: 我正在向我的朋友解释OOP。我无法回答这个问题。(我有多可耻? 我只是想逃避,因为OOP描绘了现实世界。在现实世界中,父母可以容纳孩子,但孩子不能容纳父母。OOP也是如此。我知道它很愚蠢。:P 为什么此陈述无效? 因为aChild的成员是aParent成员的超集。那为什么孩子不能容纳父母。 问题答案: 正是因为aChild是aParent功能的超集。你可以写: 因为每只狐狸都是动物。但