Unmovable struct and pointer


#1

I stumbled recently upon an artifact called unmovable struct: https://www.rust-lang.org/en-US/faq.html#how-can-i-define-a-struct-that-contains-a-reference-to-one-of-its-own-fields , which seems to be unique Rust anti-pattern :slight_smile: There two things I wasn’t able to understand related to this:

I) Can I create an unmovable struct without Cell-like wrapper? I suspect the answer is no, because

  • I can’t move referenced object, therefore I can’t create reference before I create the unmovable struct
  • To add reference later, I need a mutable (and therefore unique) reference to the filed to store reference (y in the example)
  • At the same time I need immutable reference to filed to be referenced, and that contradict thew point above

Is my reasoning correct, or am I missing something?

II) It would seem possible to create an unmovable struct on heap and then pass around pointer to it. However, the naive approach does not work: https://play.rust-lang.org/?gist=1e1579e2344fe60aedaaf3110bc7422b&version=stable . Are there any workarounds, or is it some fundamental restriction I didn’t think about?

Than you!


#2

You can use a bit of unsafe code for #2:

let ptest = Box::new(Unmovable {
        x: 42,
        y: Cell::new(None),
    });
let u: *mut Unmovable = &*ptest as *const _ as *mut _;
unsafe {
   (*u).y.set(Some(&(*u).x));
}
println!("{:?}", ptest);

let ptest2: Box<Unmovable> = ptest;

This API is bogus, however, because it makes it look like the &'a u32 outlives the Unmovable struct itself, which isn’t the case.

Take a look at the rental crate - it tries to slap a safe interface on all of this by restricting how you use the API.


#3
struct Unmovable<'a> {
    x: u32,
    y: Option<&'a mut u32>,
    z: u32,
}
fn main() {
    let mut test = Unmovable { x: 42, y: None , z: 44 };
    test.y = Some(&mut test.x);
    println!("{:?}", test.z);
}

Rust language treats the fields it has direct access to individually but blocks the whole structure. (Can move the fields)

@vitalyd code is broken

    assert_eq!(&ptest.x as *const u32, ptest.y.get().unwrap() as *const u32);
    println!("{:?}", &ptest.x as *const u32);
    let ptest2: Unmovable = *ptest;
    println!("{:?}", ptest2);
    assert_eq!(&ptest2.x as *const u32, ptest2.y.get().unwrap() as *const u32);

#4

Type casting is something I stayed away from until now :slight_smile: I take it to be “safe unsafe” code, right? As far as I understand now, the issue with naive version is that Rust borrow check for boxed objects on heap are the same for as for object on stack, while at least in theory, the former can be more relaxed, in the same vein as non-lexical lifetimes. Is it correct?

Notes to future readers: _ stands for “please derive type yourself” instruction to compiler, and you need to cast twice (similarly C++ reinterpret_cast does not cast const away, you need const_cast for that).


#5

I think that was my point, no?

I didn’t spell it out, but you can only move the box, not move out of it. Moving out of the box and putting the Unmovable on stack is essentially the same thing as grabbing a reference to the &'a i32 and then dropping the box. The compiler will no longer prevent this dangling reference, whichever way you destroy the box internals.


#6

There’s not much type casting you would do in safe Rust - pretty much scalars (e.g. widening/narrowing primitive casts) and trait objects.

AFAIK, borrowck doesn’t care (or even know) about stack vs heap. It understands regions (aka scopes) and loans (borrows). It also understands things like variance and drop rules (although this is technically handled by the dropck, but they work in tandem). The compiler currently does know about Box as a first-class citizen, but I don’t think it treats it any differently in the borrowck.

As for (somehow) supporting self-referential structs safely, there’s quite a lot of discussion about it in various places. https://internals.rust-lang.org/t/improving-self-referential-structs/4808 is but one of them but has @jpernst’s thoughts, and he probably has thought about this more than anyone else :slight_smile:.