我想内联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();
}
答案 0 :(得分:2)
我认为您在这里有些困惑。首先:在纯虚拟环境中使用特征时,不可能从特征中内联方法(也就是说:您没有有关实际类型的信息)。看起来像这样:
fn unknown_type(foo: &MyTrait) -> f32 {
foo.x()
}
在这里,编译器可能无法知道特征对象foo
后面的实际类型。因此,它被迫使用vtable并进行动态分配以调用该方法。有一种名为 devirtualization 的优化,它试图猜测正确的类型以进行静态分派(甚至是内联方法),但是这种优化有其局限性。
a.x()
会内联到(a as &StructB).x
吗?
在大多数情况下是的,但这与您的内联属性或特征对象无关。在您的小示例中,编译器可以看到整个函数,并且知道a
具有基础类型StructB
。但这又不是纯粹的虚拟上下文:编译器具有可以使用的类型信息。
另一件事:这一切与Java中的final
/ C#中的f
没有关系-如the first version of your question中所述。
这些关键字仅对类层次结构有用。由于原则上可以覆盖派生类中Java / C#中每个类的所有方法,因此,从理论上讲,编译器永远不能内联或静态分派方法。它总是必须检查vtable来检查是否存在该方法的更专业版本。当编译器具有将该方法声明为final
的类的变量时,由于可以保证不会被覆盖,因此可以静态调用它。
但是(在这个问题上)Rust中具有trait对象等同于具有接口类型的变量。而且,您(显然)不能将接口方法声明为final
。因此在这里,无论某些实现类是否将其实现声明为final
,编译器都必须执行虚拟调度。
此外,您的C ++与您的要求没有任何关系。基类A
声明带有方法主体的非虚函数。这意味着该函数在虚拟上下文中将永远不可调用。但是在您的Rust代码中,x()
没有主体,可以在虚拟上下文中使用。
在Rust中没有这样的东西,但是您可以通过impl Trait { ... }
向trait对象添加方法。具有此特征的实现者无法覆盖这些方法,因此编译器可以执行静态分派或轻松地内联该方法。
您可以看到该示例及其程序集here。
要回答我认为您实际上要问的问题:
您想内联该trait方法调用,以使其与具体类型的简单字段访问一样便宜/快速,对吧?
同样,在纯虚拟环境中是不可能的。当编译器不知道实现类型时,它不能生成简单的字段访问,因为它不知道该字段与基本指针的偏移量!我的意思是,并非所有实现类型都可以保证具有相同的内存布局,并且可以将x
始终保持在相同的位置。
但是,它可能比一种方法做的更好:代替调用getter方法,可以将字段偏移量存储在vtable中,并使用该偏移量对字段进行索引。那仍然比标准的现场访问要贵,但比方法调用要快。
这正是this RFC: "Allow fields in traits that map to lvalues in impl'ing type"中的建议。 RFC线程已关闭,但RFC仍在开发中。
所以您现在不能这样做。使用特质方法是目前最好的方法。
答案 1 :(得分:1)
根据Can #[inline] be used in both trait method declarations and implementations?和this thread on the language forum,不可能告诉编译器应内联特定方法的所有实现。但是,由于Rust是静态类型的,所以特征可以在编译时消除歧义,所以我看不出Rust编译器不能内联特定方法实现的理由。我不能肯定地说它是否会这样做,因为#[inline]
属性只是编译器可以随意忽略的建议,但是即使我们在谈论独立函数而不是在讨论独立函数,这也是正确的方法。换句话说,内联方法应该是可能的,但在任何情况下都不应依赖于内联函数。