Why are trait bounds for auto trait Send
on trait implementations ignored? (Playground(1))
trait IsSend {
fn is_send(&self);
}
impl<T: Send> IsSend for T {
fn is_send(&self) {}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let i = std::rc::Rc::new(43);
i.is_send(); // (!!) no compiler error although Rc<...> is not Send
Ok(())
}
For example using a trait bound for a self defined trait (X) it works: (Playground(2))
trait X {}
trait IsSend {
fn is_send(&self);
}
impl<T: X> IsSend for T {
fn is_send(&self) {}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let i = std::rc::Rc::new(43);
i.is_send(); // (ok) compiler error as Rc<...> does not implement X
Ok(())
}
Even more confusing, using a trait bound on a function it works as expected: (Playground(3))
fn is_send<T: Send>(_s: &T) {}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let i = std::rc::Rc::new(43);
is_send(&i); // (ok) compiler as Rc<...> is not Send
Ok(())
}
It looks like the auto trait Send
(or auto traits in general) is treated specially. However, I have not found any documentation about this. Is this a bug or just my lack of understanding :-)?
Have a look at this slightly modified version of your playground
use std::any::TypeId;
trait IsSend {
fn is_send(&self);
}
impl<T: Send + 'static> IsSend for T {
fn is_send(&self){
println!("TypeId of T: {:?}", TypeId::of::<T>());
}
}
fn main() -> Result<(),Box<dyn std::error::Error>> {
println!("TypeId of i32: {:?}", TypeId::of::<i32>());
println!("TypeId of Rc<i32>: {:?}", TypeId::of::<std::rc::Rc<i32>>());
let i = std::rc::Rc::new(43);
i.is_send(); // (!!) no compiler error although Rc is not Send
Ok(())
}
And we have the result:
TypeId of i32: TypeId { t: 13431306602944299956 }
TypeId of Rc<i32>: TypeId { t: 1918842018359854094 }
TypeId of T: TypeId { t: 13431306602944299956 }
I changed:
println!
calls so that we can see the TypeId of the typesT: 'static
, due to TypeId
constraints. This should not affect our answer, since both Rc<i32>
and i32
are 'static
.It can be seen that T
is resolved as i32
instead of Rc<i32>
. That is, is_send
is called with T = i32
rather than T = Rc<i32>
.
This is because Rc<T>
implements Deref<Target = T>
. When you call i.is_send()
, it is actually equivalent to (*i).is_send()
, and *i
is an i32
, which is a Send
. The compiler attempts to perform dereferencing when you use the dot operator to call a method on a value until the type bounds are satisfied.
To show this, let's try changing Rc
to Arc
, where Arc
implements Send
. You can see that T
now has the same TypeId as Arc<i32>
rather than i32
. This is because Arc
already satisfies the T: Send
bound, and no further dereferencing is required.
use std::any::TypeId;
use std::sync::Arc;
trait IsSend {
fn is_send(&self);
}
impl<T: Send + 'static> IsSend for T {
fn is_send(&self){
println!("TypeId of T: {:?}", TypeId::of::<T>());
}
}
fn main() -> Result<(),Box<dyn std::error::Error>> {
println!("TypeId of i32: {:?}", TypeId::of::<i32>());
println!("TypeId of Arc<i32>: {:?}", TypeId::of::<Arc<i32>>());
let i = Arc::new(43);
i.is_send(); // (!!) no compiler error although Rc is not Send
Ok(())
}
TypeId of i32: TypeId { t: 13431306602944299956 }
TypeId of Arc<i32>: TypeId { t: 3504454246784010795 }
TypeId of T: TypeId { t: 3504454246784010795 }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With