Skip to content

Incremental compilation breaks(?) Rc::ptr_eq with trait objects across function call boundary #104446

Closed
@isaacthefallenapple

Description

@isaacthefallenapple

I found this question on SO where two more or less identical calls to Rc::ptr_eq return different results, depending on where the Rc<RefCell<_>> is cast to a trait object but also how the program is compiled. It behaves inconsistently when the function in question is not inlined or when incremental compilation is turned on. I've whittled it down to a smaller example (though it can probably be reduced further):

use std::{cell::RefCell, rc::Rc};

struct S;
trait T {}
impl T for S {}

struct C {
    r: Rc<RefCell<dyn T>>,
}

impl C {
    fn has_dyn_other(&self, other: &Rc<RefCell<dyn T>>) -> bool {
        Rc::ptr_eq(&self.r, other)
    }
    fn has_other<A: T + 'static>(&self, other: &Rc<RefCell<A>>) -> bool {
        Rc::ptr_eq(&self.r, &(Rc::clone(other) as Rc<RefCell<dyn T>>))
    }
}

#[test]
fn weird_trait_obj() {
    let s = Rc::new(RefCell::new(S));
    let c = C {
        r: Rc::clone(&s) as Rc<RefCell<dyn T>>,
    };

    // passes
    assert!(Rc::ptr_eq(&(Rc::clone(&s) as Rc<RefCell<dyn T>>), &c.r));
    // passes
    assert!(Rc::ptr_eq(
        &(Rc::clone(&s) as Rc<RefCell<dyn T>>),
        &(Rc::clone(&s) as Rc<RefCell<dyn T>>)
    ));
    // passes
    assert!(c.has_dyn_other(&(Rc::clone(&s) as Rc<RefCell<dyn T>>)));
    // passes with `incremental = false` or `#[inline(always)]`, fails otherwise
    assert!(c.has_other(&s));
}

I expected to see this happen: I would have thought either all or none of the asserts would pass regardless of how it's compiled. I don't know how Rc<RefCell<T>> as Rc<RefCell<dyn Trait>> actually works, so I'm not sure which one. I'm guessing it looks more like Rc<dyn RefCell<Trait>> where the Rc points at a trait object that in turn points at the RefCell as its instance? I don't know how else it might work.

Instead, this happened: That last test is sensitive to at least two distinct factors: is incremental compilation on and is C::has_other inlined or not. Adding #[inline(always)] makes the assert pass regardless of incremental compilation.

Meta

rustc --version --verbose:

rustc 1.65.0 (897e37553 2022-11-02)
binary: rustc
commit-hash: 897e37553bba8b42751c67658967889d11ecd120
commit-date: 2022-11-02
host: x86_64-unknown-linux-gnu
release: 1.65.0
LLVM version: 15.0.0
Backtrace

thread 'weird_trait_obj' panicked at 'assertion failed: c.has_other(&s)', src/lib.rs:37:5
stack backtrace:
   0: rust_begin_unwind
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panicking.rs:584:5     
   1: core::panicking::panic_fmt
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/panicking.rs:142:14   
   2: core::panicking::panic
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/panicking.rs:48:5     
   3: rcrefcelltest::weird_trait_obj
             at ./src/lib.rs:37:5
   4: rcrefcelltest::weird_trait_obj::{{closure}}
             at ./src/lib.rs:21:1
   5: core::ops::function::FnOnce::call_once
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/ops/function.rs:248:5 
   6: core::ops::function::FnOnce::call_once
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/ops/function.rs:248:5 

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-bugCategory: This is a bug.T-libsRelevant to the library team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions