Skip to content

Allow moving out of &'static mut T #125868

Open

Description

@Ddystopia

Hello, it is perfectly sound to move out of &'static mut T and have that reference consumed, as no safe code can access T under the reference after that point.

struct NotDefaultNotCopy([u8; 20]);

fn main() {
    let foo: &'static mut NotDefaultNotCopy = Box::leak(Box::new(NotDefaultNotCopy([0; 20])));
    let foo_taken = *foo;

    println!("{:?}", foo_taken.0);
}

For example, miri is happy with that code if take in unsafely written (but detects leak) playground

Here is code with more unsafe but with no miri leak warnings: playground

Activity

  1. added
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Jun 1, 2024
  2. workingjubilee commented on Jun 1, 2024

    @workingjubilee
    Member

    Miri also doesn't complain about this program, one which is surely not what you intend: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=4a15c73887bce9565554ebf5b12e3627

  3. added
    C-discussionCategory: Discussion or questions that doesn't represent real issues.
    and removed
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Jun 1, 2024
  4. saethlin commented on Jun 1, 2024

    @saethlin
    Member

    @Ddystopia your take does not move out, it does a typed copy. The demonstration you want to write cannot be written in surface Rust because you can't get past the compiler frontend. That's the whole point of this issue.

    To write the demo you want, you must use custom MIR: https://doc.rust-lang.org/nightly/std/intrinsics/mir/index.html

  5. QuineDot commented on Jun 1, 2024

    @QuineDot

    Original(?) discussion started here.

  6. saethlin commented on Jun 1, 2024

    @saethlin
    Member

    In any case, I think the basic reasoning here is wrong: The 'static bound doesn't help because you can reborrow a &'static mut into another &'static mut.

    If the following entirely safe code were to be allowed to compile, we would have access to a moved-from value:

    #[derive(Debug)]
    struct NotCopy(u8);
    
    fn main() {
        let x: &'static mut NotCopy = Box::leak(Box::new(NotCopy(0u8)));
        let y: &'static mut NotCopy = &mut *x;
        let z = *y;
        println!("{:?}", *x); // oops, use of moved-from value
    }
  7. QuineDot commented on Jun 2, 2024

    @QuineDot

    The reborrow used to create y makes x unsuable for 'static (forever). The state of x after the move is akin to how replace_with functions. Or std::mem::replace (for the duration of the method body), for that matter.

    There's more discussion in this RFC thread.

    I personally don't feel there are strong enough upsides for language support, e.g. DerefMove for &'static mut T. But either (the originally posted) fn take is sound or replace_with is unsound.

    fn take<T: Send>(reference: &'static mut T) -> T {
        let (sender, receiver) = std::sync::mpsc::channel();
        std::thread::spawn(move || {
            replace_with::replace_with_or_abort(reference, |t| {
                let _ = sender.send(t);
                loop {}
            })
        });
        
        receiver.recv().unwrap()
    }

    replace_with isn't available on the playground or I'd link a runnable example. But an example usage is

        let foo: &'static mut NotDefaultNotCopy = Box::leak(Box::new(NotDefaultNotCopy([1; 20])));
        let reborrowed_even = &mut *foo;
        let foo_taken = take(reborrowed_even);
    
        println!("{:?}", foo_taken.0);
        // Trying to use `foo` is an error because `*foo` is exclusively reborrowed forever
  8. saethlin commented on Jun 2, 2024

    @saethlin
    Member

    Thanks for the correction!

  9. Ddystopia commented on Jun 2, 2024

    @Ddystopia
    ContributorAuthor

    I do agree that there might not be urging need for this, but it sometimes might be useful during embedded development and if something will change with borrow checker's attitude to moving out of mutable references, then it might not take a lot of extra work to consider adding this feature, which will not break anything and apparently sound.

  10. QuineDot commented on Jun 2, 2024

    @QuineDot

    which will not break anything and apparently sound.

    There was a potentially breaking use case in the RFC thread, depending on how the decision goes. As far as I know there hasn't been an FCP on the topic. That said, opinions did seem favorable on the side of this being sound.

    That RFC was closed in part due to concerns about adding an API that aborts. I will note that a proposal to add a function like take in the OP (which is limited to the &'static mut _ case) also gives an opportunity to bless the operation as sound without introducing an aborting API (and without trying to tackle unwinding as a whole).

    Blessing this operation means that every &'static mut T can bet converted into a newtype which is analogous to a &'static mut Option<T> (which you can move T into and, conditionally, out of; conditionally borrow; etc).

  11. LunarLambda commented on Jun 2, 2024

    @LunarLambda
  12. WaffleLapkin commented on Jun 2, 2024

    @WaffleLapkin
  13. LunarLambda commented on Jun 2, 2024

    @LunarLambda
  14. WaffleLapkin commented on Jun 2, 2024

    @WaffleLapkin
  15. LunarLambda commented on Jun 2, 2024

    @LunarLambda
  16. WaffleLapkin commented on Jun 2, 2024

    @WaffleLapkin
  17. LunarLambda commented on Jun 2, 2024

    @LunarLambda
  18. Ddystopia commented on Jun 2, 2024

    @Ddystopia
    ContributorAuthor

    Miri also doesn't complain about this program, one which is surely not what you intend: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=4a15c73887bce9565554ebf5b12e3627

    Thank you for note! Here is another example, which is closer to what I wanted to demonstrate playground

    I also tried custom MIR, but compiler kept ICE-ing and I gave up

  19. RalfJung commented on Jun 10, 2024

    @RalfJung
    Member

    I agree that this is sound:

    fn take<T>(reference: &'static mut T) -> T {
        unsafe { core::ptr::read(reference) }
    }

    It would be fairly easy to prove this in Rustbelt as well.

    Not sure if it's worth having special support for this in the borrow checker, a dedicated method seems more clear / less magic to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-discussionCategory: Discussion or questions that doesn't represent real issues.T-langRelevant to the language teamT-opsemRelevant to the opsem team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions