Suppose you have these code in your lib.rs
:
struct MyType(String);
trait MyTrait {
fn shared_behavior(&self);
}
impl MyTrait for MyType {
fn shared_behavior(&self) {
println!("{}", self.0)
}
}
#[test]
fn test() {
let my_var = MyType(String::from("Hello"));
let my_ref = &my_var;
my_ref.shared_behavior();
my_var.shared_behavior();
let my_rc = Rc::new(MyType(String::from("World")));
my_rc.shared_behavior();
}
All of the above worked.
But if I create the following function:
fn do_it_twice(value: impl MyTrait) {
value.shared_behavior();
value.shared_behavior();
}
and then change the test function to:
#[test]
fn test() {
let my_var = MyType(String::from("Hello"));
let my_ref = &my_var;
// do_it_twice(my_ref); // the trait bound `&MyType: MyTrait` is not satisfied [E0277]
do_it_twice(my_var);
let my_rc = Rc::new(MyType(String::from("World")));
// do_it_twice(my_rc); // the trait bound `std::rc::Rc<MyType>: MyTrait` is not satisfied [E0277]
}
then my_ref
(&MyType
) and my_rc
(Rc<MyType>
) stopped working.
Mod #1 (reference
)
fn do_it_twice(value: &impl MyTrait) {
value.shared_behavior();
value.shared_behavior();
}
#[test]
fn test() {
let my_var = MyType(String::from("Hello"));
let my_ref = &my_var;
do_it_twice(my_ref);
// do_it_twice(my_var); // mismatched types [E0308] expected `&impl MyTrait+Sized`, found `MyType`
let my_rc = Rc::new(MyType(String::from("World")));
// do_it_twice(my_rc); // mismatched types [E0308] expected `&impl MyTrait+Sized`, found `Rc<MyType>`
}
Mod #2 (Borrow
)
fn do_it_twice<T: MyTrait>(value: impl Borrow<T>) {
value.borrow().shared_behavior();
value.borrow().shared_behavior();
}
#[test]
fn test() {
let my_var = MyType(String::from("Hello"));
let my_ref = &my_var;
// do_it_twice(my_ref);
// ^ type annotations needed [E0282] cannot infer type for type parameter `T` declared on the function `do_it_twice`
do_it_twice::<MyType>(my_ref);
do_it_twice(my_var);
let my_rc = Rc::new(MyType(String::from("World")));
// do_it_twice(my_rc);
// ^ type annotations needed [E0282] cannot infer type for type parameter `T` declared on the function `do_it_twice`
do_it_twice::<MyType>(my_rc);
}
Mod #3 (AsRef
)
impl AsRef<MyType> for MyType {
#[inline]
fn as_ref(&self) -> &MyType {
self
}
}
fn do_it_twice<T: MyTrait>(value: impl AsRef<T>) {
value.as_ref().shared_behavior();
value.as_ref().shared_behavior();
}
#[test]
fn test() {
let my_var = MyType(String::from("Hello"));
let my_ref = &my_var;
do_it_twice(my_ref);
do_it_twice(my_var);
let my_rc = Rc::new(MyType(String::from("World")));
do_it_twice(my_rc);
}
Mod #4 (Deref
)
fn do_it_twice(value: impl Deref<Target=impl MyTrait>) {
value.shared_behavior();
value.shared_behavior();
}
#[test]
fn test() {
let my_var = MyType(String::from("Hello"));
let my_ref = &my_var;
do_it_twice(my_ref);
// do_it_twice(my_var);
// ^ the trait bound `MyType: std::ops::Deref` is not satisfied [E0277]
// the trait `std::ops::Deref` is not implemented for `MyType`
// Note: required by a bound in `do_it_twice`
// Help: consider borrowing here
let my_rc = Rc::new(MyType(String::from("World")));
do_it_twice(my_rc);
}
Mod #5 (Blanket impl
for Deref
)
impl<M: MyTrait, T: Deref<Target = M>> MyTrait for T
{
#[inline]
fn shared_behavior(&self) {
(**self).shared_behavior()
}
}
fn do_it_twice(value: impl MyTrait) {
value.shared_behavior();
value.shared_behavior();
}
#[test]
fn test() {
let my_var = MyType(String::from("Hello"));
let my_ref = &my_var;
do_it_twice(my_ref);
do_it_twice(my_var);
// (if the following line is uncommented): cannot move out of `my_var` because it is borrowed [E0505]
// do_it_twice(my_ref);
let my_rc = Rc::new(MyType(String::from("World")));
do_it_twice(my_rc);
}
Comparison
Mod #1 is the simpliest way and, although it required a few more &
marks to pass in MyType
values (compared to the original version), it wouldn't consume the parameter as MyTrait::shared_behavior()
only needed a reference to self
.
All the alternative solutions consumed the various other input types (technically the reference is also consumed, but it's copied before that), and this side effect may or may not be intended.
Of course, we can add some restrictions, like for mod #5 we can do:
fn do_it_twice(value: impl MyTrait + Copy) {
value.shared_behavior();
value.shared_behavior();
}
so that any type that doesn't impl Copy
wouldn't get consumed accidentally, but wait! Our blanket impl
is for T: Deref
, and the only type that implements both Copy
and Deref
in the standard library is the shared reference type &T
.
So we're basically doing:
impl<T: MyTrait> MyTrait for &T {
#[inline]
fn shared_behavior(&self) {
(**self).shared_behavior()
}
}
and
fn do_it_twice(value: impl MyTrait + Copy) {
value.shared_behavior();
value.shared_behavior();
}
is now equivalent to:
fn do_it_twice(value: &impl MyTrait) {
value.shared_behavior();
value.shared_behavior();
}
for any type that impl MyTrait
but does not impl Copy
.
Conclusion
Covering all the babysitting is tempting, but it may cause more confusion as the side effects are often not expected.
Unless we expect the concrete type to impl Copy
, it'd be better to just stick with passing &impl MyTrait
s.
Even if the type does impl Copy
, the best we can do is merely reducing a tiny &
mark at the call site.
What's your opinion on this topic? Do you have better solutions?