TLDR: If I have a trait object of type &MyTrait
, should it be possible to pass to a function that accepts T where T: MyTrait
?
Consider a trait MyTrait
and an implementation of that trait (in this case MyStruct
):
trait MyTrait {
fn f(&self, i32) {}
}
struct MyStruct;
impl MyTrait for MyStruct {}
There are 4 distinct interesting signatures for functions that want to take MyTrait
as a parameter:
fn use_generic<T: MyTrait>(_: T) {}
fn use_generic_borrow<T: MyTrait>(_: &T) {}
fn use_generic_borrow_dst<T: MyTrait+?Sized>(_: &T) {}
fn use_trait_object(_: &MyTrait) {}
Here are various ways to call the functions. I think I understand how trait objects, dynamically sized types and monomorphization work so all of these make sense to me. I have included "as &MyTrait"
explicitly where a trait object is passed:
use_generic(MyStruct); // ok
use_generic_borrow(&MyStruct); // ok
use_generic_borrow_dst(&MyStruct); // ok
use_generic_borrow_dst(&MyStruct as &MyTrait); // ok
use_trait_object(&MyStruct as &MyTrait); // ok
use_generic_borrow(&MyStruct as &MyTrait); // not ok
// this makes sense to me; the function may do things for which it needs a size for T
//~^^ the trait `core::marker::Sized` is not implemented for the type `MyTrait`
My question is about this case:
use_generic(&MyStruct as &MyTrait);
//~^ the trait `MyTrait` is not implemented for the type `&MyTrait`
I can make this work by explicitly providing the following "obvious" implementation:
impl<'a> MyTrait for &'a MyTrait {
fn f(&self, x: i32) {
(*self).f(x);
}
}
- Specifically for traits
T
that are object-safe, is it always possible to construct this type of "obvious" impl T for &T
that simply delegates to *self
?
- If it is not always possible, what is a counterexample?
- If it is always possible, what would be the downside of allowing a trait object
&MyTrait
to be passed as T where T: MyTrait
without having to write out the "obvious" impl?
Specifically for traits T that are object-safe, is it always possible to construct this type of "obvious" impl T for &T
that simply delegates to *self
?
This is always possible if the all of the trait methods take &self
. It is not possible if any method takes &mut self
or self
or Box<Self>
.
2 Likes
Thanks for the quick response. If any method takes self
or Box<Self>
then my understanding is the trait is not object-safe, so they are not relevant to my question.
If any method takes &mut self
then the "obvious" implementation looks like:
impl<'a> MyTrait for &'a mut MyTrait {
fn f(&mut self, x: i32) {
(*self).f(x);
}
}
which enables you to call:
use_generic(&mut MyStruct as &mut MyTrait);
So it seems the answer to question 1 is yes, it is always possible. Then I would be interested to hear some answers for question 3:
If it is always possible, what would be the downside of allowing a trait object &MyTrait
to be passed as T where T: MyTrait
without having to write out the "obvious" impl?
Automatically implementing MyTrait
for &MyTrait
would make it a backwards incompatible change to add a new (defaulted) method that takes anything other than &self
.
@sfackler isn't that already true? In the following code, uncommenting any of the new (defaulted) methods breaks the existing code.
trait ChangedTrait {
fn f(&self) {}
//fn g() {}
//fn h(self) {}
//fn i(&mut self) {}
}
trait OtherTrait {
fn i(&mut self) {}
}
struct MyStruct;
impl ChangedTrait for MyStruct {}
impl OtherTrait for MyStruct {}
fn main() {
let _: &ChangedTrait = &MyStruct;
MyStruct.i();
}
It's even worse than that - adding even an inherent method can break downstream code for the same reason. It's the price you pay for having an "open" trait system, in comparison to something like Java where all interfaces implemented are declared in the type definition.
To deal with this, we don't consider the addition of a defaulted method to be a breaking change even though it can break code. RFC 1105 has more details on why for this case and many others.
One key thing to note here is that code that gets broken can change itself in such a way that it'll work with versions of the breaking crate both before and after the change by invoking the method with UFCS syntax: OtherTrait::i(&MyStruct)
.