# If Cell/etc are invariant then how do trait objects work?

**URL:** <https://internals.rust-lang.org/t/if-cell-etc-are-invariant-then-how-do-trait-objects-work/15193>\
**Category:** Uncategorized\
**Created:** [August 18, 2021, 11:03am UTC](https://internals.rust-lang.org/t/if-cell-etc-are-invariant-then-how-do-trait-objects-work/15193 "2021-08-18T11:03:37Z")\
**Posts on this page:** 19\
**Page:** 1

<div class="post-metadata">

**Author:** ![Soni](https://avatars.discourse-cdn.com/v4/letter/s/a3d4f5/32.png) [@Soni](https://internals.rust-lang.org/u/Soni)\
**Post date:** [August 18, 2021, 11:03am UTC](https://internals.rust-lang.org/t/if-cell-etc-are-invariant-then-how-do-trait-objects-work/15193/1 "2021-08-18T11:03:37Z")

</div>

We're confused about variance, after having read this: [Subtyping and Variance - The Rustonomicon](https://doc.rust-lang.org/nomicon/subtyping.html)

This seems to imply trait objects follow variance? But it seems more subtle than what is documented, because e.g. this seemingly works:

```rust
use std::cell::RefCell;
struct Foo;
struct Why;
trait Bar {}
impl Bar for Foo {}
impl Bar for Why {}

fn main() {
    let x: &RefCell<Foo> = &RefCell::new(Foo);
    let y: &RefCell<dyn Bar> = x;
    y.borrow_mut();
    x.borrow_mut();
}

```

How does variance actually work? Does it even matter for traits?

---

<div class="post-metadata">

**Author:** ![H2CO3](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/h2co3/32/2849_2.png) [@H2CO3](https://internals.rust-lang.org/u/H2CO3)\
**Post date:** [August 18, 2021, 11:31am UTC](https://internals.rust-lang.org/t/if-cell-etc-are-invariant-then-how-do-trait-objects-work/15193/2 "2021-08-18T11:31:31Z")

</div>

As far as I know, Rust only has proper subtyping and variance with regards to lifetimes. Thus I'm not sure the concept of variance even applies to traits or trait objects _alone_ (unless they themselves involve lifetimes).

Specifically what in your example above would you expect not to work?

---

<div class="post-metadata">

**Author:** ![SkiFire13](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/skifire13/32/7579_2.png) [@SkiFire13](https://internals.rust-lang.org/u/SkiFire13)\
**Post date:** [August 18, 2021, 12:05pm UTC](https://internals.rust-lang.org/t/if-cell-etc-are-invariant-then-how-do-trait-objects-work/15193/3 "2021-08-18T12:05:14Z")

</div>

The reason is the same as why coercing an `&mut T` to `&mut dyn Trait` is sound: because with that `dyn Trait` you can't do anything more than you could with just `T`.

---

<div class="post-metadata">

**Author:** ![Soni](https://avatars.discourse-cdn.com/v4/letter/s/a3d4f5/32.png) [@Soni](https://internals.rust-lang.org/u/Soni)\
**Post date:** [August 18, 2021, 12:10pm UTC](https://internals.rust-lang.org/t/if-cell-etc-are-invariant-then-how-do-trait-objects-work/15193/4 "2021-08-18T12:10:51Z")

</div>

So an \<T: 'static\> is invariant?

---

<div class="post-metadata">

**Author:** ![crlf0710](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/crlf0710/32/1987_2.png) [@crlf0710](https://internals.rust-lang.org/u/crlf0710)\
**Post date:** [August 18, 2021, 12:53pm UTC](https://internals.rust-lang.org/t/if-cell-etc-are-invariant-then-how-do-trait-objects-work/15193/5 "2021-08-18T12:53:45Z")

</div>

This is unsizing coercion, and there's a `CoerceUnsized` impl within `std` implemented for `RefCell` to support this.

---

<div class="post-metadata">

**Author:** ![Soni](https://avatars.discourse-cdn.com/v4/letter/s/a3d4f5/32.png) [@Soni](https://internals.rust-lang.org/u/Soni)\
**Post date:** [August 18, 2021, 1:03pm UTC](https://internals.rust-lang.org/t/if-cell-etc-are-invariant-then-how-do-trait-objects-work/15193/6 "2021-08-18T13:03:05Z")

</div>

No that's for pointers:

> For wrapper types that directly embed `T` like `Cell<T>` and `RefCell<T>` , [...]. This will let coercions of types like `Cell<Box<T>>` work.

---

<div class="post-metadata">

**Author:** ![crlf0710](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/crlf0710/32/1987_2.png) [@crlf0710](https://internals.rust-lang.org/u/crlf0710)\
**Post date:** [August 18, 2021, 1:16pm UTC](https://internals.rust-lang.org/t/if-cell-etc-are-invariant-then-how-do-trait-objects-work/15193/7 "2021-08-18T13:16:43Z")

</div>

You're right, it's using `Unsize` trait directly through the `CoerceUnsized` implementation on shared references in this case:

```rust
#![feature(unsize)]

use std::cell::RefCell;
use std::marker::Unsize;

fn unsize<A, B: ?Sized>(v: &A) -> &B where A:Unsize<B> {
    v
}

struct Foo;
struct Why;
trait Bar {}
impl Bar for Foo {}
impl Bar for Why {}

fn main() {
    let x: &RefCell<Foo> = &RefCell::new(Foo);
    unsize::<RefCell<Foo>, RefCell<dyn Bar>>(&x);
}

```

---

<div class="post-metadata">

**Author:** ![steffahn](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/steffahn/32/13288_2.png) [@steffahn](https://internals.rust-lang.org/u/steffahn)\
**Post date:** [August 18, 2021, 2:44pm UTC](https://internals.rust-lang.org/t/if-cell-etc-are-invariant-then-how-do-trait-objects-work/15193/8 "2021-08-18T14:44:47Z")

</div>

> [@Soni](#):
>
> How does variance actually work? Does it even matter for traits?

I’ve wondered the same thing recently after/while pondering on unsized coercion in the context of finding [#85099](https://github.com/rust-lang/rust/issues/85099). Turns out, unsized coercion it mostly separate from subtyping. In effect converting a `T` into an `S`, which isn’t allowed.

The _reason_ why `RefCell<T>` is invariant in `T` is that otherwise, you could have `S` subtype of `T`, take an `&RefCell<S>`, coerce it into `&RefCell<T>`, then _write_ a `T` into that, and read the value back as an `S` from a copy of the original `&RefCell<S>` reference.

Now, unsizing _does_ feel similar to subtying in a lot of ways. There’s a type `T` and another type `dyn Trait` and you’ve got an “is-a” relationship between them. Yet, the coercion from `T` to `dyn Trait` only works in types where `T` exists behind _exactly one_ layer of indirection. So something like `&RefCell<T>` to `&RefCell<dyn Trait>` works. But then, this conversion doesn’t present any way of replacing the `dyn Trait` inside of that `RefCell` with a different value of type `dyn Trait` like it was possible for the types `S` and `T` above. Why? Well, you cannot more any `dyn Trait` values because they’re unsized, and any `self: &RefCell<dyn Trait>` method from `Trait` that can modify the `RefCell`’s contents calls a sound method on the underlying type `&RefCell<T>`, so it doesn’t really offer any way to exploit it either.

The reasoning above isn’t much more than: Turns out the types being unsized and the way trait objects work just don’t offer any way to create unsoundness. I don’t find it particularly convincing myself since I cannot really get any deep intuition about _why_ no way to create unsoundness exists; I just know that the ways I tried don’t seem to offer any weak points.

If for example we’d try to eliminate the problem of being unable to write to the `RefCell` because it contains an unsized type, we might _box_ the type, so use `&RefCell<Box<T>>`. But that’s _two_ indirections now so the unsound coercion is gone… We could remove the outer reference and, well…, now we can convert `RefCell<Box<T>>` into `RefCell<Box<dyn Trait>>`! But that’s not a particularly dangerous operation; it could be achieved by `RefCell::into_inner` and `RefCell::new`. In fact, even for subtypes `S` of `T`, direct conversion of `RefCell<S>` to `RefCell<T>` aren’t problematic from a soundness perspective (yet they are disallowed by variance; you’d need to do the `into_inner`+`new` approach here); having `RefCell` be invariant is somewhat of a hack that’s only there to ensure that _shared references_ (like `&`, `Rc` or `Arc`) to `RefCell`s aren’t covariant.

* * *

Another interesting point on the interaction of trait objects and variance: There are subtle cases of what works and what doesn’t work when you’re comparing `dyn for<'a> Foo<'a>` with `dyn Foo<'b>`.

Certainly, intuitively a `dyn for<'a> Foo<'a>` _is a_ `dyn Foo<'b>`, so this is subtyping-like. And in fact it _does_ interact with subtyping/variance, so something like

```rust
trait Foo<'a> {}

fn foo<'b>(x: Box<Box<Box<dyn for<'a> Foo<'a>>>>) -> Box<Box<Box<dyn Foo<'b>>>> {
    x
}

```

compiles. But the same coercion appears to _also_ work in contexts where unsized coercion would be allowed. For example this also compiles

```rust
fn bar<'b>(x: Box<RefCell<dyn for<'a> Foo<'a>>>) -> Box<RefCell<dyn Foo<'b>>> {
    x
}

fn bar_1<'b>(x: RefCell<Box<dyn for<'a> Foo<'a>>>) -> RefCell<Box<dyn Foo<'b>>> {
    x
}

fn bar_2<'b>(x: RefCell<Box<RefCell<dyn for<'a> Foo<'a>>>>) -> RefCell<Box<RefCell<dyn Foo<'b>>>> {
    x
}

```

but it doesn’t compose with other covariant wrappers, e.g.

```rust
fn baz<'b>(x: Box<Box<RefCell<dyn for<'a> Foo<'a>>>>) -> Box<Box<RefCell<dyn Foo<'b>>>> {
    x
    // error[E0308]: mismatched types
    // --> src/lib.rs:14:5
    // |
    // 22 | x
    // | ^ one type is more general than the other
    // |
    // = note: expected struct `Box<Box<RefCell<(dyn Foo<'b> + 'static)>>>`
    // found struct `Box<Box<RefCell<(dyn for<'a> Foo<'a> + 'static)>>>`
}

```

or

```rust
fn qux<'b>(x: Box<RefCell<Box<dyn for<'a> Foo<'a>>>>) -> Box<RefCell<Box<dyn Foo<'b>>>> {
    todo!() // wouldn’t work either
}

```

and here, at least the last example ~~`qux` would also be _unsound_~~ if it _did_ compile successfully. _Edit:_ In my head I was thinking of something like `&RefCell<Box<dyn Foo<'b>>` or `Arc<RefCell<Box<dyn Foo<'b>>>` which would _actually_ be unsound. With a `Box` of course it’s _not_ a problem, in fact it [can be implemented](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=718bcf15b172ff9a174267583afc20c7) by dereferencing and `Box::new`.

([all code examples in a playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=7cc15e29ef1581e7daf3ce14fb9415fe))

* * *

I do believe that all of this isn’t sufficiently documented in the reference, at least as far as I’m aware. Especially the way that `for<'a> Foo<'a>` worked is something I could only find out by trying out what compiles and what doesn’t. Looks like the functions `bar[_(1|2)]` aren’t even compiling on not-too-recent compilers. Seems like they’re only accepted since Rust `1.46` (no mention of this [in the release notes](https://github.com/rust-lang/rust/blob/master/RELEASES.md#version-1460-2020-08-27) though).

---

<div class="post-metadata">

**Author:** ![Soni](https://avatars.discourse-cdn.com/v4/letter/s/a3d4f5/32.png) [@Soni](https://internals.rust-lang.org/u/Soni)\
**Post date:** [August 18, 2021, 2:56pm UTC](https://internals.rust-lang.org/t/if-cell-etc-are-invariant-then-how-do-trait-objects-work/15193/9 "2021-08-18T14:56:53Z")

</div>

We _think_ the thing with unsizing is that the vtable pointer is... actually external to the invariant element.

That is to say, a `&RefCell<dyn Bar>` is, effectively, a `(&RefCell<Foo>, &'static VTable<dyn Bar, Foo>)` (with the `<Foo>` parts elided), rather than a form of subtyping. Any attempts to write to it wouldn't/cannot work, because you can't change the storage size or the vtable of the original object. Of course, `Foo` itself (and the `Bar for Foo`) is able to do whatever it wants to the underlying storage.

But it's not particularly intuitive.

---

<div class="post-metadata">

**Author:** ![quinedot](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/quinedot/32/7294_2.png) [@quinedot](https://internals.rust-lang.org/u/quinedot)\
**Post date:** [August 18, 2021, 11:28pm UTC](https://internals.rust-lang.org/t/if-cell-etc-are-invariant-then-how-do-trait-objects-work/15193/10 "2021-08-18T23:28:42Z")

</div>

`for<> dyn Trait` and `dyn Trait<>` do have a subtyping relationship as far as I'm aware, just like `for<> fn` and `fn`. And I'm also not aware of any other higher-ranked types (vs. bounds); if there are any, I _imagine_ they act the same as well. (You can use this to compare against unsized coercion: If I update your `baz` to use `fn`s, I get the same error, for example.)

[More here](https://github.com/rust-lang/rust/issues/85863#issuecomment-872536139) (which you're aware of but others may not be).

* * *

This conversation as a whole so far has been ignoring `dyn`'s elision-happy lifetime bound. Every `dyn Trait` has one, which is basically the limit of all the lifetimes that got erased. Surprisingly, this bound acts covariant-like in a way that _bypasses invariance_ as part of unsized coercion.

```rust
use core::cell::RefCell;
trait Foo<'a> {}

// works
fn foo_1<'a: 'b, 'b, 'c>(x: Box<RefCell<dyn Foo<'c> + 'a>>) -> Box<RefCell<dyn Foo<'c> + 'b>> {
    x
}

// fails
fn foo_2<'a: 'b, 'b, 'c>(x: Box<RefCell<dyn Foo<'c> + 'b>>) -> Box<RefCell<dyn Foo<'c> + 'a>> {
    x
}

```

This is working as specified in [RFC 599](https://rust-lang.github.io/rfcs/0599-default-object-bound.html#detailed-design) (skip forward to " **Interaction with object coercion**") as best I can tell ([which isn't 100%](https://users.rust-lang.org/t/the-confessional-thread-parts-of-rust-that-i-still-dont-get-after-all-this-time/39022/30)).

See [this previous discussion](https://users.rust-lang.org/t/solved-variance-of-dyn-trait-a/39733/) for more on this from Ralf, eddyb, Yandros and others.

---

<div class="post-metadata">

**Author:** ![Soni](https://avatars.discourse-cdn.com/v4/letter/s/a3d4f5/32.png) [@Soni](https://internals.rust-lang.org/u/Soni)\
**Post date:** [August 18, 2021, 11:58pm UTC](https://internals.rust-lang.org/t/if-cell-etc-are-invariant-then-how-do-trait-objects-work/15193/11 "2021-08-18T23:58:44Z")

</div>

We haven't been able to do a safe lifetime transmute with it, at least. We don't know how you'd break it.

```rust
use core::cell::RefCell;
trait Foo<'a> {
  fn thing(&mut self) -> &mut &'a str;
}

impl<'a, 'b> Foo<'b> for &'a mut &'b str {
  fn thing(&mut self) -> &mut &'b str {
      self
  }
}

fn foo_1<'a: 'b, 'b, 'c>(x: &'b RefCell<dyn Foo<'c> + 'a>) -> &'b RefCell<dyn Foo<'c> + 'b> {
    x
}

fn foo_2<'a, 'b>(x: &'a mut &'b str) {
    let x: RefCell<&'a mut &'b str> = RefCell::new(x);
    let y = foo_1(&x);
    let z = foo_3(y, &String::new());
}

fn foo_3<'b: 'c, 'c>(x: &'b RefCell<dyn Foo<'c> + 'b>, y: &'b str) {
    *x.borrow_mut().thing() = y;
}

```

But we're not sure how this would interact with the hypothetical Arc locking, with `Invariant<T>`: [Arc locking by SoniEx2 · Pull Request #88112 · rust-lang/rust · GitHub](https://github.com/rust-lang/rust/pull/88112)

---

<div class="post-metadata">

**Author:** ![atagunov](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/atagunov/32/5877_2.png) [@atagunov](https://internals.rust-lang.org/u/atagunov)\
**Post date:** [August 19, 2021, 1:13pm UTC](https://internals.rust-lang.org/t/if-cell-etc-are-invariant-then-how-do-trait-objects-work/15193/12 "2021-08-19T13:13:49Z")

</div>

> [@steffahn](#):
>
> the coercion from `T` to `dyn Trait` only works in types where `T` exists behind _exactly one_ layer of indirection

Literally an eye-opener! 👍

---

<div class="post-metadata">

**Author:** ![Soni](https://avatars.discourse-cdn.com/v4/letter/s/a3d4f5/32.png) [@Soni](https://internals.rust-lang.org/u/Soni)\
**Post date:** [August 19, 2021, 5:25pm UTC](https://internals.rust-lang.org/t/if-cell-etc-are-invariant-then-how-do-trait-objects-work/15193/13 "2021-08-19T17:25:12Z")

</div>

T: 'static is not invariant. (found by @SkiFire13 ) [Rust Playground](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=a70acf048225b351d6620c226661e8d3)

So unfortunately one would actually need `Invariant<T>` for [Arc locking](https://github.com/rust-lang/rust/pull/88112), but nobody wants `Invariant<T>`. (We're also still not sure if this is sound, even with `Invariant<T>`, altho `Invariant<T>` is sufficient to block the above example.)

---

<div class="post-metadata">

**Author:** ![quinedot](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/quinedot/32/7294_2.png) [@quinedot](https://internals.rust-lang.org/u/quinedot)\
**Post date:** [August 21, 2021, 3:07am UTC](https://internals.rust-lang.org/t/if-cell-etc-are-invariant-then-how-do-trait-objects-work/15193/14 "2021-08-21T03:07:47Z")

</div>

> [@steffahn](#):
>
> Seems like they’re only accepted since Rust `1.46` (no mention of this [in the release notes](https://github.com/rust-lang/rust/blob/master/RELEASES.md#version-1460-2020-08-27) though).

[move leak-check to during coherence, candidate eval](https://github.com/rust-lang/rust/pull/72493) landed in `1.46.0`. I'm not sure it's that but seems like it could be.

---

<div class="post-metadata">

**Author:** ![dhm](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/dhm/32/4879_2.png) [@dhm](https://internals.rust-lang.org/u/dhm)\
**Post date:** [August 21, 2021, 1:24pm UTC](https://internals.rust-lang.org/t/if-cell-etc-are-invariant-then-how-do-trait-objects-work/15193/15 "2021-08-21T13:24:19Z")

</div>

> [@quinedot](#):
>
> Every `dyn Trait` has one, which is basically the limit of all the lifetimes that got erased. Surprisingly, this bound acts covariant-like in a way that _bypasses invariance_ as part of unsized coercion.

Tbh, when I saw the thread title, I immediately thought of that 😄. So the thing is that, for instance, a `&'a mut (dyn Trait + 'static)` can be used where a `&'a mut (dyn Trait + 'bound)` is expected, which looks like covariance (especially in that thread at the time); which is why I've since learned that to make sure we are dealing with covariance, wrapping the stuff within a layer that doesn't allow this "coercion" to happen is best

- _e.g._, when wondering if `T : U`, rather than checking with `|it: T| -> U { it }`, it's better to check with `|it: *const T| -> *const U { it }` or `|it: (T, )| -> (U, ) { it }` since the last two prevent coercions from taking place (and correctly error when `T = &'a mut (dyn Trait + 'long)` and `U = &'a mut (dyn Trait + 'short)`, for instance, as eddyb pointed out at the time).

- regarding the coercion, we already know from the unsizing coercion that `&'a mut (impl Sized + Tr + 'lt)` can be coerced to a `&'a mut (dyn Tr + 'lt)`; and if that is sound, it's not based on the `Sized` bound, so we ought to be able to remove that from the left and say that `&'a mut (impl ?Sized + Tr + 'lt)` could _theoretically_ (it wouldn't be unsound, although it may _technically_ be impossible in some cases) be coerced to a `&'a mut (dyn Tr + 'lt)`, which yields that it is not absurd that `&'a mut (dyn Tr + 'long)` can be coerced to a `&'a mut (dyn Tr + 'short)`. How and when that "reunsizing" coercion is implemented by the compiler exactly is another question (one which I can't totally answer yet), but at least it's not something that should be theoretically surprising when we look at it this way 🙂

---

<div class="post-metadata">

**Author:** ![RustyYato](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/rustyyato/32/13627_2.png) [@RustyYato](https://internals.rust-lang.org/u/RustyYato)\
**Post date:** [August 21, 2021, 1:29pm UTC](https://internals.rust-lang.org/t/if-cell-etc-are-invariant-then-how-do-trait-objects-work/15193/16 "2021-08-21T13:29:51Z")

</div>

> [@dhm](#):
>
> and correctly error when `T = &'a mut (dyn Trait + 'long)` and `U = &'a mut (dyn Trait + 'short)` , for instance, as eddyb pointed out at the time

The prior discussion being referenced here

> **[\[Solved\] Variance of \`dyn Trait + 'a\`](https://users.rust-lang.org/t/solved-variance-of-dyn-trait-a/39733)**
>
> Continuing the discussion from The Confessional Thread: Parts of Rust that I still don't get after all this time: cc @Yandros, @ExpHp I think this is a bug, I tried out some more variations // none of these compile // struct Foo\<'x\>(dyn...

---

<div class="post-metadata">

**Author:** ![steffahn](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/steffahn/32/13288_2.png) [@steffahn](https://internals.rust-lang.org/u/steffahn)\
**Post date:** [August 21, 2021, 1:42pm UTC](https://internals.rust-lang.org/t/if-cell-etc-are-invariant-then-how-do-trait-objects-work/15193/17 "2021-08-21T13:42:22Z")

</div>

> [@dhm](#):
>
> "reunsizing" coercion

That’s a nice term for this 😁

---

<div class="post-metadata">

**Author:** ![steffahn](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/steffahn/32/13288_2.png) [@steffahn](https://internals.rust-lang.org/u/steffahn)\
**Post date:** [August 21, 2021, 1:48pm UTC](https://internals.rust-lang.org/t/if-cell-etc-are-invariant-then-how-do-trait-objects-work/15193/18 "2021-08-21T13:48:57Z")

</div>

> [@dhm](#):
>
> it's better to check with `|it: *const T| -> *const U { it }` or `|it: (T, )| -> (U, ) { it }` since the last two prevent coercions from taking place

I think that tuples might eventually implement

```rust
impl<T, U> CoerceUnsized<(U,)> for (T,) where T: CoerceUnsized<U> {}

```

so the second approach isn’t 100% future-proof. However, pointers (or anything with indirections) should keep “working” (i.e. failing to compile) indefinitely.

---

<div class="post-metadata">

**Author:** ![system](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/system/32/14092_2.png) [@system](https://internals.rust-lang.org/u/system)\
**Post date:** [November 19, 2021, 1:49pm UTC](https://internals.rust-lang.org/t/if-cell-etc-are-invariant-then-how-do-trait-objects-work/15193/19 "2021-11-19T13:49:39Z")

</div>

Closed on November 19, 2021, 1:49pm UTC

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.
