I want to inline TraitA::x()
method without knowing who implements TraitA
.
For example:
(Note: StructB
implements 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.
The method will be implemented the same way in different structs.
trait TraitA {
fn x(&self) -> f32;
}
struct StructB {
x: f32
}
impl TraitA for StructB {
#[inline]
fn x(&self) -> f32 {
self.x
}
}
Will a.x()
get inlined to (a as &StructB).x
?
I can do this fine on C++, for example:
class A {
float _x;
public:
float x() {
return _x;
}
};
struct B : A {};
int main() {
A* a = new B;
a->x();
}
I think you are confusing a few things here. First of all: it's impossible to inline a method from a trait when you are using it in purely virtual context (that is: you have no information about the actual type). This would look like this:
fn unknown_type(foo: &MyTrait) -> f32 {
foo.x()
}
Here, the compiler can't possibly know the actual type behind the trait object foo
. Thus, it is forced to use the vtable and do dynamic dispatch to call the method. There is an optimization called devirtualization that tries to guess the correct type to do static dispatch (or even inline the method), but this optimization has its limits.
Will
a.x()
get inlined to(a as &StructB).x
?
In most cases yes, but this has nothing to do with your inline-attribute nor with trait objects. In your tiny example, the compiler can see the whole function and knows that a
has the underlying type StructB
. But again, this is not a purely virtual context: the compiler has type information it can use.
Another thing: this all has nothing to do with final
in Java/f
in C# -- as mentioned in the first version of your question.
These keywords are only useful for class hierarchies. Since you can, in principle, override all methods of every class in Java/C# in a derived class, the compiler could in theory never inline or statically dispatch a method. It would always have to check the vtable to check if there is a more specialized version of this method. When the compiler has a variable of a class that declares that method as final
, it can statically call it, since it is guaranteed that it's not overridden.
But having a trait object in Rust is (regarding this question) equivalent to having a variable with an interface type. And you (clearly) cannot declare an interface method as final
. So here, the compiler has to do virtual dispatch regardless of whether some implementing classes declare their implementation as final
.
Furthermore, your C++ doesn't have anything to do with what you're asking. The base class A
declares a non-virtual function with a method body. That means that the function will never be callable in virtual context. But in your Rust code x()
does not have a body and can be used in virtual context.
In Rust there is nothing like that, but you could add methods to the trait object via impl Trait { ... }
. Implementors of that trait are not able to override those methods, so the compiler can do static dispatch or inline the method easily.
You can see that example with its assembly here.
To answer what I think you are actually asking:
You want to inline that trait method call so that it's as cheap/fast as a simple field access of a concrete type, right?
That's, again, not possible in purely virtual context. When the compiler doesn't know the implementing type, it cannot generate a simple field access, since it doesn't know the offset of that field from the base pointer! I mean, not all implementing types are guaranteed to have the same memory layout and keep x
at always the same position.
However, one could do better than a method: instead of calling the getter method, it would be possible to store the field offset in the vtable and use that to index the field. That would still be more expensive than a standard field access, but faster than a method call.
That's exactly what's proposed in this RFC: "Allow fields in traits that map to lvalues in impl'ing type". The RFC thread is closed, but the RFC is still developing.
So you can't do that now. Using a trait method is the best you can do right now.