Thought 4: Alternative Universes
Have you ever seen where Self: Sized
on a trait’s function? What does that mean? And how can we use this feature to make String
implement Copy
?
As an example of this in the wild see Read::by_ref
. Read
is ment to be object safe so you can do &mut dyn Read
, but how is that possible if one of it’s methods returns a &mut Self
?
trait Read {
fn by_ref(&mut self) -> &mut Self;
}
fn demo(x: &mut dyn Read) {}
fn main() {}
error[E0038]: the trait `Read` cannot be made into an object
--> src/main.rs:5:17
|
5 | fn demo(x: &mut dyn Read) {
| ^^^^^^^^ `Read` cannot be made into an object
|
note: 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>
--> src/main.rs:2:29
|
1 | trait Read {
| ---- this trait cannot be made into an object...
2 | fn by_ref(&mut self) -> &mut Self;
| ^^^^^^^^^ ...because method `by_ref` references the `Self` type in its return type
= help: consider moving `by_ref` to another trait
See it can’t be a trait object! But … what if we add a bound that Self
needs to not a trait object? But how to do that? Well trait objects are dynamically sized types so they don’t implement Sized
. So lets force Self
to implement Sized
and see what rustc says.
trait Read {
fn by_ref(&mut self) -> &mut Self
where
Self: Sized;
}
fn demo(x: &mut dyn Read) {}
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.06s
It works! When its a trait object the by_ref
method doesn’t exist so its allowed to be a trait object.
This opens a question. What happens if we do a blanket impl where the Self
type is allowed to not implement Sized
?
trait Read {
fn by_ref(&mut self) -> &mut Self
where
Self: Sized;
}
impl<T: ?Sized> Read for T {
fn by_ref(&mut self) -> &mut Self
where
Self: Sized
{
self
}
}
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.05s
Rustc accepts this… Well what if we just do it for a type that isn’t Sized
directly. It definitely won’t allow it then, right?
trait Read {
fn by_ref(&mut self) -> &mut Self
where
Self: Sized;
}
impl Read for [i32] {
fn by_ref(&mut self) -> &mut Self
where
Self: Sized
{
self
}
}
error[E0277]: the size for values of type `[i32]` cannot be known at compilation time
--> src/main.rs:10:9
|
10 | Self: Sized
| ^^^^^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `Sized` is not implemented for `[i32]`
= help: see issue #48214
help: add `#![feature(trivial_bounds)]` to the crate attributes to enable
|
1 + #![feature(trivial_bounds)]
|
See rustc isn’t that weird. Its not going to allow us to do this. Makes sense right? Well add a for<'a>
to that where bound on the impl.
trait Read {
fn by_ref(&mut self) -> &mut Self
where
Self: Sized;
}
impl Read for [i32] {
fn by_ref(&mut self) -> &mut Self
where
for<'a> Self: Sized
{
self
}
}
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.05s
Oh no. It allowed it… Why? Because the for<'a>
makes rustc check the bound later than normal because … reasons.
Wait nothing about Sized
is special. What if we do this with Copy
?
trait Thing {
fn need_copy(&self)
where
Self: Copy;
}
impl Thing for String {
fn need_copy(&self)
where
for<'a> Self: Copy
{
let x = *self;
let y = x;
let z = y;
dbg!(x, y, z);
}
}
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.05s
That’s right we made String
implement Copy
and we used it in the code. To prove to yourself String
really does implement Copy
here you can try changing the method body and rustc will treat it as it would any other code.
We have successfully made an alternative universe where String
implements Copy
. Something that should be impossible. Well if its an alternative universe I guess rustc doesn’t care about it.