Why can a trait not construct itself? - rust

This code give me a compilation error:
trait IBoo {
fn new() -> Box<IBoo>;
}
while this code compiles without any error:
trait IBoo {
//fn new() -> Box<IBoo>;
}
trait IFoo {
fn new() -> Box<IBoo>;
}
Why does the first one not compile? rustc --explain E0038 does not give me a direct hint why it is not possible.
Is it possible to combine construction and methods in one interface (trait)?

This is from the description of E0038:
Method has no receiver
Methods that do not take a self parameter can't be called since
there won't be a way to get a pointer to the method table for them.
trait Foo {
fn foo() -> u8;
}
This could be called as <Foo as Foo>::foo(), which would not be able
to pick an implementation.
Adding a Self: Sized bound to these methods will generally make this
compile.
trait Foo {
fn foo() -> u8 where Self: Sized;
}
You can do this:
trait IBoo {
fn new() -> Box<IBoo>
where
Self: Sized;
}
In other cases, you can place the restriction on the entire impl:

The compiler tells you the exact reason this doesn't work:
error[E0038]: the trait `IBoo` cannot be made into an object
--> src/main.rs:2:5
|
2 | fn new() -> Box<IBoo>;
| ^^^^^^^^^^^^^^^^^^^^^^ the trait `IBoo` cannot be made into an object
|
= note: method `new` has no receiver
Note the last line. It's telling you that the reason for the error is that new() doesn't depend on having an instance of a value implementing the IBoo trait.
By not taking a pointer of some kind to self, the method cannot be invoked by dynamic dispatch. If it can't be invoked by dynamic dispatch, this means it cannot go into the trait's associated vtable. There has to be an associated vtable, because that's how something like Box<IBoo> works. Some time ago, the core Rust developers decided that including even a single "non-object safe" method in a trait disqualified the whole trait from being used as an object.
To put it in other words: because you've defined a method that cannot be dynamically dispatched, the IBoo trait as a whole is disqualified from being used with dynamic dispatch.
If you want some kind of constructor function, you need to have some other way of writing that. This could be using plain function pointers, or an IBooFactory trait, as you might with Java.

Related

Using HRTBs to automate getter method unit tests

I have a "view" data structure in Rust that contains a reference to another data structure, and I want to automate testing of the getter methods that I implemented on the view data structure. Here's a MRE with a single getter:
/// View data structure.
struct View<'a> {
value: &'a u32,
}
impl<'a> View<'a> {
/// Getter method.
pub fn value(&self) -> u32 {
*self.value
}
}
/// Attempt to automate unit testing of the getter: create an underlying value,
/// then create a `View`, then apply the getter method to the `View` and ensure
/// we get the expected value out.
fn check_getter(getter: fn(&View) -> u32) {
let value: u32 = 7;
let view = View { value: &value };
assert!(getter(&view) == 7);
}
check_getter(View::value);
This fails to compile, with the following error:
mismatched types
expected fn pointer `for<'r, 's> fn(&'r tests::test_adcs::View<'s>) -> _`
found fn pointer `for<'r> fn(&'r tests::test_adcs::View<'_>) -> _`
I think I need to use HRTBs, and have tried various permutations, but haven't been able to get anything to compile.
TL;DR: Use a closure (you can use macros if you prefer):
check_getter(|v| v.value());
You fell into the trap of early-bound vs. late-bound lifetimes.
View::value does not have the signature for<'a> fn <View<'a>>::value(). Rather, it has the signature fn <View<'some_concrete_lifetime>>::value(). That is, 'a is not any lifetime, but some inferred lifetime. Therefore HRTB doesn't work - only one lifetime matches the given function. You can read more about early-bound and late-bound lifetimes in this recent answer I wrote, but the crux of the issue is that because the lifetime 'a appears on the impl, not the function itself, it becomes early-bound and has only one concrete lifetime for each instance of the function instead of being late-bound and allowed to use with any lifetime.
The fix is just to wrap it with a closure, making it late-bound.

Adding an iterator of a trait on another trait

I have a trait which has a function that should return an iterator of a different trait. I also need a common function which takes this trait and does operations on it.
This is the code I have currenty:
pub trait NodeLike : Sized {
/// Get the node id
fn get_id(&self) -> i32;
}
pub trait EdgeLike {
/// Get the destination node id
fn get_node_id(&self) -> i32;
/// Get the edge id
fn get_edge_id(&self) -> i32;
/// iterates of the source nodes
fn iter_source_nodes(&self) -> dyn Iterator<Item = dyn NodeLike>;
^^^^^^^^^^^^^
}
fn operation(id: i32, edge: dyn EdgeLike) {
for node in edge.iter_source_nodes().find(|n| n.get_id() == id) {
}
}
The underlined part above throws the compiler error:
the trait `NodeLike` cannot be made into an object
`NodeLike` cannot be made into an object rustc E0038
main.rs(1, 22): for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
Reading through the documentation, I believe my NodeLike trait is object-safe. The only function it has is &Self (i.e. &self)
Now, I can further my code by removing the Sized trait on NodeLike. However, then the error occurs in the function operation; I cannot use the find function on the iterator.
How can I change my code so that I can make NodeLike object safe, AND be able to return an iterator which I can call find on?
Following Stargateur's suggestion and your playground link, I got it to compile like this:
Playground link
This is if you do not want to use any unstable features.
The trick is to specify the explicit type of the iterator. If you don't know that type, just put any stupid type in and let the compiler complain about the type mismatch. It'll tell you that we're dealing here with a slice iter, so hence the std::slice::Iter.
The other modifications are then about adding a lifetime parameter to your struct, because iter() returns references. If you don't want that, change it to into_iter, but then you must use self instead of &self.
Also, for your operation, you must do some lifetime magic...
fn operation<'a, Item: NodeLike>(id: i32, edge: &'a impl EdgeLike<'a, Item=Item> )
If you are okay with unstable features, you can avoid having to specify the exact type of the iter: In your playground version just change the dyn to impl. The compiler will tell you that that's an unstable feature and will show you the macro to use.
Finally, to avoid having to add lifetime annotations to the trait itself, there's something about generic associated types, but maybe that's going too far for now.

Differences between 2 styles of default implementations in a trait?

There are 2 ways to provide methods for a Trait itself, Rustdoc distinguishes them by saying "provided methods" and impl dyn XXX. For example:
trait Trait {
fn foo(&self) {
println!("Default implementation");
}
}
impl Trait {
fn bar(&self) {
println!("Anonymous implementation?");
}
}
I noticed it when I was reading the documentation of Rust's failure crate.
What are the use cases for them? What are the differences?
The first snippet,
trait Trait {
fn foo(&self) {
println!("Default implementation");
}
}
implements a provided method on the trait. This method can be overridden by a trait implementation, but it does not have to be overridden.
The second snippet,
impl Trait {
fn bar(&self) {
println!("Anonymous implementation?");
}
}
implements an inherent method on a trait object of type dyn Trait. Method implementations for dyn Trait can only be called for trait objects, e.g. of type &dyn Trait. They can't receive self by value, since dyn Trait does not have a size known at compile time, and they can't be called on concrete types implementing Trait (including generic types with a Trait bound).
The modern notation is to write impl dyn Trait instead of impl Trait, and in fact this notation was one of the motivating examples for the introduction of the dyn keyword – the old syntax did not provide any clues as to what the semantics are, whereas the new syntax with the dyn keyword hints at the fact that this impl is only used together with dynamic dispatch.
A trait object is a fat pointer to an object implementing Trait, but the concrete type of the object is not necessarily known at compile time. The fat pointer contains a pointer to the object data, as well as a pointer to the virtual method table of the object type. The latter is used to dynamically dispatch to the correct trait implementation at runtime.
It is rather uncommon to use impl dyn Trait. Generally it's only useful if you want to make use of some dynamic type information, like downcasting to the actual type. The only traits with inherent methods on trait objects in the standard library are Any and Error.
In short: one can be overridden, and the other cannot.
When you define a trait, you define items that implementations of the trait may (or have to) override:
trait Trait {
fn foo(&self) {
println!("Default implementation");
}
}
impl Trait for i64 {
fn foo(&self) {
println!("i64 implementation: {}", self);
}
}
On the other hand, using impl Trait, you define inherent methods, which cannot be overridden:
impl Trait {
fn bar(&self) {
self.foo();
self.foo()
}
}
// Try:
impl Trait for i64 {
fn bar(&self) { ... } // error: bar cannot be overridden.
}
As a result, inherent traits methods act as the Template Method Pattern: they provide a canvas linking together one or multiple overridable method(s).
If you look at the failure crate that you linked, the method Failure::find_root_cause() states:
This is equivalent to iterating over iter_causes() and taking the last item.
You may consider those inherent methods to be convenience methods, methods providing an easy/intuitive interface for common tasks which can be accomplished manually... but are conveniently pre-defined.
Note: any inherent method could be implemented as a free function taking the trait as a first argument; however free functions cannot be called in method position.

Why does returning `Self` in trait work, but returning `Option<Self>` requires `Sized`?

This trait definition compiles fine:
trait Works {
fn foo() -> Self;
}
This, however, does lead to an error:
trait Errors {
fn foo() -> Option<Self>;
}
error[E0277]: the size for values of type `Self` cannot be known at compilation time
--> src/lib.rs:6:5
|
6 | fn foo() -> Option<Self>;
| ^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `std::marker::Sized` is not implemented for `Self`
= note: to learn more, visit <https://doc.rust-lang.org/book/second-edition/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
= help: consider adding a `where Self: std::marker::Sized` bound
= note: required by `std::option::Option`
With the : Sized supertrait bound, it works.
I know that the Self type in traits is not automatically bound to be Sized. And I understand that Option<Self> cannot be returned (via the stack) unless it is sized (which, in turn, requires Self to be sized). However, the same would go for Self as return type, right? It also cannot be stored on the stack unless it's sized.
Why doesn't the first trait definition already trigger that error?
(This question is related, but it doesn't answer my exact question – unless I didn't understand it.)
There are two sets of checks happening here, which is why the difference appears confusing.
Each type in the function signature is checked for validity. Option inherently requires T: Sized. A return type that doesn't require Sized is fine:
trait Works {
fn foo() -> Box<Self>;
}
The existing answer covers this well.
Any function with a body also checks that all of the parameters are Sized. Trait functions without a body do not have this check applied.
Why is this useful? Allowing unsized types to be used in trait methods is a key part of allowing by-value trait objects, a very useful feature. For example, FnOnce does not require that Self be Sized:
pub trait FnOnce<Args> {
type Output;
extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}
fn call_it(f: Box<dyn FnOnce() -> i32>) -> i32 {
f()
}
fn main() {
println!("{}", call_it(Box::new(|| 42)));
}
A big thanks to pnkfelix and nikomatsakis for answering my questions on this topic.
It's in the error message:
= note: required by `std::option::Option`
Option requires the type to be Sized because it allocates on the stack. All type parameters to a concrete type definition are bound to Sized by default. Some types choose to opt out with a ?Sized bound but Option does not.
Why doesn't the first trait definition already trigger that error?
I think this is a conscious design decision, due to history, future-proofing and ergonomics.
First of all, Self is not assumed to be Sized in a trait definition because people are going to forget to write where Self: ?Sized, and those traits would be less useful. Letting traits be as flexible as possible by default is a sensible design philosophy; push errors to impls or make developers explicitly add constraints where they are needed.
With that in mind, imagine that trait definitions did not permit unsized types to be returned by a method. Every trait method that returns Self would have to also specify where Self: Sized. Apart from being a lot of visual noise, this would be bad for future language development: if unsized types are allowed to be returned in the future (e.g. to be used with placement-new), then all of these existing traits would be overly constrained.
The issue with Option is just the tip of the iceberg and the others have already explained that bit; I'd like to elaborate on your question in the comment:
is there a way I can implement fn foo() -> Self with Self not being
Sized? Because if there is no way to do that, I don't see the point in
allowing to return Self without a Sized bound.
That method indeed makes it impossible (at least currently) to utilize the trait as a trait object due to 2 issues:
Method has no receiver:
Methods that do not take a self parameter can't be called since there
won't be a way to get a pointer to the method table for them.
Method references the Self type in its arguments or return type:
This renders the trait not object-safe, i.e. it is impossible to create a trait object from it.
You are still perfectly able to use it for other things, though:
trait Works {
fn foo() -> Self;
}
#[derive(PartialEq, Debug)]
struct Foo;
impl Works for Foo {
fn foo() -> Self {
Foo
}
}
fn main() {
assert_eq!(Foo::foo(), Foo);
}

Rust Trait object conversion

The following code won't compile due to two instances of this error:
error[E0277]: the trait bound Self: std::marker::Sized is not satisfied
I don't understand why Sized is required in this instance as both &self and &Any are pointers and the operation does not require knowledge of the size of the structure that implements the trait, it only requires knowledge of the pointer itself and the type it is converting from and to, which it will have because &self is generic when implemented inside a trait.
I think this may be an instance of the compiler enforcing unnecessary constraints and I've considered filing an issue with the rust-lang GitHub repo but I figured I should probably see if someone here knows something I don't before I go file an issue.
use std::any::Any;
trait Component: Any {
fn as_any(&self) -> &Any {
self
}
fn as_any_mut(&mut self) -> &mut Any {
self
}
}
The alternative to this is to make as_any() and as_any_mut() required functions for the structs that implement this trait, but for those structures the implementation would always be exactly as displayed here down to each individual character, resulting in several instances of identical boilerplate code.
Dynamically sized types can also implement traits. In particular, when you define an object-safe trait, the compiler also defines a dynamically sized type with the same name as the trait, which lets you use object types such as &Component.
Object types such as &Component or &Any are not just ordinary pointers; they're fat pointers. A fat pointer combines a pointer to the data and another piece of data: for object types, it's a pointer to the vtable; for slices, it's the length of the slice.
When casting from a regular pointer (e.g. a &Button) to an object type, the compiler statically knows which vtable to put in the fat pointer (e.g. Button's vtable for Any). On the other hand, Rust doesn't support casting from an object type to another object type (e.g. from &Component to &Any), because there's not enough data in an object to initialize the new fat pointer. This is why the compiler adds this note to the error message:
= note: required for the cast to the object type `std::any::Any + 'static`
There are two ways to fix this:
Require that all types implementing Component be Sized:
trait Component: Any + Sized {
fn as_any(&self) -> &Any {
self
}
fn as_any_mut(&mut self) -> &mut Any {
self
}
}
This has the consequence that you will not be able to use object types such as &Component or Box<Component> at all.
Make the as_any and as_any_mut methods only available when Self is Sized:
trait Component: Any {
fn as_any(&self) -> &Any
where Self: Sized
{
self
}
fn as_any_mut(&mut self) -> &mut Any
where Self: Sized
{
self
}
}
This way, you can still use object types for the trait, but you will not be able to call as_any and as_any_mut on them.
I found what I consider to be a great solution that didn't require new compiler features.
pub trait Component {
// ...
}
pub trait ComponentAny: Component + Any {
fn as_any(&self) -> &Any;
fn as_any_mut(&mut self) -> &mut Any;
}
impl<T> ComponentAny for T
where T: Component + Any
{
fn as_any(&self) -> &Any {
self
}
fn as_any_mut(&mut self) -> &mut Any {
self
}
}
From here, I just change all my APIs to accept ComponentAny instead of Component. Because Any is automatically implemented for any 'static type, ComponentAny is now automatically implemented for any 'static type that implements Component. Thanks to Is there a way to combine multiple traits in order to define a new trait? for the idea.

Resources