首页 > 解决方案 > 内联一个常用方法

问题描述

我想在TraitA::x()不知道谁实现的情况下内联方法TraitA

例如:

(注:StructB实现TraitA。)

let a: &TraitA = &StructB { x: 0f32 };
a.x(); // This should access `a.x` field directly
       // as `TraitA::x()` always return the same field,
       // the same `b.x` field slot.

该方法将在不同的结构中以相同的方式实现。

trait TraitA {
    fn x(&self) -> f32;
}

struct StructB {
    x: f32
}

impl TraitA for StructB {
    #[inline]
    fn x(&self) -> f32 {
        self.x
    }
}

a.x()内联到(a as &StructB).x?

我可以在 C++ 上做到这一点,例如:

class A {
    float _x;

public:
    float x() {
        return _x;
    }
};

struct B : A {};

int main() {
    A* a = new B;
    a->x();
}

标签: rustinline

解决方案


我认为你在这里混淆了一些事情。首先:当您在纯虚拟上下文中使用它时,不可能从特征中内联方法(即:您没有关于实际类型的信息)。这看起来像这样:

fn unknown_type(foo: &MyTrait) -> f32 {
    foo.x()
}

在这里,编译器不可能知道 trait object 背后的实际类型foo。因此,它被迫使用vtable并进行动态调度来调用该方法。有一种称为去虚拟化的优化,它试图猜测正确的类型来进行静态调度(甚至内联方法),但这种优化有其局限性。

a.x()内联到(a as &StructB).x?

在大多数情况下是的,但这与您的 inline-attribute 或 trait 对象无关。在您的小示例中,编译器可以看到整个函数并知道它a具有底层类型StructB。但同样,这不是一个纯粹的虚拟上下文:编译器有它可以使用的类型信息。


另一件事:这一切都与finalJava/ fC# 中的所有内容无关——正如您的问题的第一个版本中所述

这些关键字仅对类层次结构有用。由于原则上您可以在派生类中覆盖 Java/C# 中每个类的所有方法,因此编译器理论上永远不会内联或静态调度方法。它总是必须检查 vtable 以检查是否有此方法的更专业版本。当编译器有一个类的变量将该方法声明为final时,它可以静态调用它,因为它保证它不会被覆盖。

但是在 Rust 中拥有一个 trait 对象(关于这个问题)等同于拥有一个具有接口类型的变量。而且您(显然)不能将接口方法声明为final. 所以在这里,无论某些实现类是否将它们的实现声明为final.


此外,您的 C++ 与您的要求没有任何关系。基类A声明了一个带有方法体的非虚函数。这意味着该函数将永远无法在虚拟上下文中调用。但是在您的 Rust 代码x()中没有主体,可以在虚拟上下文中使用。

在 Rust 中没有类似的东西,但你可以通过impl Trait { ... }. 该特征的实现者无法覆盖这些方法,因此编译器可以轻松地进行静态调度或内联方法。

您可以在此处查看该示例及其程序集。


要回答我认为您实际上在问的问题:

您想内联该特征方法调用,使其与具体类型的简单字段访问一样便宜/快速,对吗?

同样,这在纯虚拟环境中是不可能的。当编译器不知道实现类型时,它无法生成简单的字段访问,因为它不知道该字段相对于基指针的偏移量!我的意思是,并非所有实现类型都保证具有相同的内存布局并x始终保持在相同的位置。

但是,可以比方法做得更好:而不是调用 getter 方法,可以将字段偏移量存储在 vtable 中并使用它来索引字段。这仍然比标准字段访问更昂贵,但比方法调用更快。

这正是本 RFC 中提出的内容:“允许特征中的字段映射到 impl'ing 类型中的左值”。RFC 线程已关闭,但 RFC 仍在开发中。

所以你现在不能这样做。使用 trait 方法是你现在能做的最好的事情。


推荐阅读