Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider removing UB-happy `static mut` in a future edition. #53639

Open
eddyb opened this Issue Aug 23, 2018 · 62 comments

Comments

Projects
None yet
@eddyb
Copy link
Member

eddyb commented Aug 23, 2018

static mut is almost impossible to use correctly, see rust-lang-nursery/lazy-static.rs#117 for an example in the widely-used lazy-static.

You must be able to show that every borrow of the static mut is not reentrant (as opposed to regular interior mutability, which only requires reentrance-freedom when accessing data), which is almost entirely impossible in real-world scenarios.


We have a chance at removing it from Rust2018 and force people to use a proper synchronization abstraction (e.g. lazy_static! + Mutex), or in lieu of one, thread_local! / scoped_thread_local!.

If they were using static mut with custom synchronization logic, they should do this:

pub struct CustomSynchronizingAbstraction<T> {
    /* UnsafeCell / Cell / RefCell / etc. around data, e.g. `T` */
}
// Promise that proper synchronization exists *around accesses*.
unsafe impl<T: Sync> Sync for CustomSynchronizingAbstraction<T> {}

And then use CustomSynchronizingAbstraction with regular statics, safely.

This matches the "soundness boundary" of Rust APIs, whereas static mut is more like C.

cc @RalfJung @rust-lang/compiler @rust-lang/lang

@Centril

This comment has been minimized.

Copy link
Contributor

Centril commented Aug 23, 2018

I don't know at all whether we should do this or not atm. But to make this decision I have an...

...Idea: We should do a crater run to scrutinize how many legitimate usages there are and how many ones there are that have UB.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Aug 23, 2018

cc @rust-lang/wg-unsafe-code-guidelines -- we have this group just for this sort of thing =)

@alercah

This comment has been minimized.

Copy link
Contributor

alercah commented Aug 23, 2018

I'm 100% behind getting rid of static mut. Any attempt to use it which isn't UB is replaceable with something else which is safe, likely a Mutex or something thread local. Or, per the original comment, a custom alternate type implementing Sync directly.

@joshtriplett

This comment has been minimized.

Copy link
Member

joshtriplett commented Aug 23, 2018

We should clearly document the undefined behavior, but I don't think we should remove the ability to have (unsafe) global mutable locations directly, without any layer of indirection. That would make it more difficult to build certain types of lock-free synchronization, for instance.

@eddyb

This comment has been minimized.

Copy link
Member Author

eddyb commented Aug 23, 2018

@joshtriplett But you can always use unsafe impl Sync + static?

@alercah

This comment has been minimized.

Copy link
Contributor

alercah commented Aug 23, 2018

Can you give an example of what you mean? You can't do much more with static mut than you could with a static UnsafeCell.

@RalfJung

This comment has been minimized.

Copy link
Member

RalfJung commented Aug 23, 2018

But you can always use unsafe impl Sync + static?

Yeah, and that's just as unsafe. So TBH I do not see the point.

@eddyb

This comment has been minimized.

Copy link
Member Author

eddyb commented Aug 23, 2018

@RalfJung No, it's not, and I've explained why. With unsafe impl Sync, you only have to prove the data accesses correct, but with static mut, the references themselves can conflict.

EDIT: Now if "references exist" cannot be UB, ever, then this is not a problem, but IIUC, they do.
Also, you can't use a static mut from safe code, only a static. If the static mut is used correctly, why spread unsafety when you can wrap it in a sound API.

@alercah

This comment has been minimized.

Copy link
Contributor

alercah commented Aug 23, 2018

It's not actually different from an UnsafeCell if we replace each &var with unsafe {&*var.get()}, right? Then the aliasing rules would be the same; the difference is that with the UnsafeCell you can still keep references to the cell itself?

@RalfJung

This comment has been minimized.

Copy link
Member

RalfJung commented Aug 23, 2018

@eddyb Ah okay I see -- that has nothing to with with unsafe impl tough and everything with &UnsafeCell.

I agree &UnsafeCell is safer than static mut.

@eddyb

This comment has been minimized.

Copy link
Member Author

eddyb commented Aug 23, 2018

@RalfJung Okay I should make it clearer that the unsafe impl is for "custom synchronization abstraction". I'll go edit the issue description.

@RalfJung

This comment has been minimized.

Copy link
Member

RalfJung commented Aug 23, 2018

If we could weaken static mut to only give you raw pointers, that could help...

... tough people would probably still turn them into references ASAP.

@japaric

This comment has been minimized.

Copy link
Member

japaric commented Aug 23, 2018

If they were using static mut with custom synchronization logic, they should do this:

How would they instantiate their custom type in stable Rust? User cons fns are unstable and even if we do stabilize min_const_fn that doesn't include anything that has bounds so even with that your example won't compile on stable.

To me this sounds like it would reduce what you can do in the 2018 edition. In the 2015 edition you can create static mut variables that contain primitive types but in the 2018 edition you can not do the equivalent unless I'm missing something like adding a RacyCell type with const constructor to core. (Here I'm assuming that it's not certain whether min_const_fn will make it into the edition release).

@eddyb

This comment has been minimized.

Copy link
Member Author

eddyb commented Aug 23, 2018

@japaric Okay, that's a good point. I removed the bound from the struct definition, does that help? If you use it with a T: !Sync type you just can't put it in a static, but it should be fine otherwise.

Note that you can use an associated constant if you don't need arguments in your constructor (e.g. lazy-static already uses Lazy::<T>::INIT for this pattern).
Otherwise, yes, the min_const_fn requirement is a problem.

Depending on what you're doing you might be able to make your fields public, but as was in the case of lazy-static, that's not always the case.

cc @Centril @oli-obk Do we want to force people to replace their "custom synchronization abstractions" involving static muts with ones that require const fn?

In the 2015 edition you can create static mut variables that contain primitive types

What kinds of types? Atomics with relaxed ordering should probably be preferred either way even in single-threaded code, unless you have some sort of proof that interrupts are disabled.
(I'm assuming that you just can't trust anything to be single-threaded in user-space, in a library, and in kernel-space / embedded you have interrupts to worry about, wrt reentrance-safety)

like adding a RacyCell type with const constructor to core

Hmm, RacyCell<PubliclyConstructableType> would be okay, I think, you'd just have to implement methods on it (so you'd wrap it in another publicly-constructible-type, or use an extension trait).

Given pub struct Abstraction(pub RacyCell<AbstractionData>);, I think that &'static Abstraction is significantly safer than static mut but only if it gives you a *mut AbstractionData, not a &mut.

I wish the min_const_fn idea popped up sooner and we stabilized it already 😢.
Another 3 years of randomly finding unsound static mut uses doesn't sound fun.

@eddyb

This comment has been minimized.

Copy link
Member Author

eddyb commented Aug 23, 2018

@rfcbot poll @rust-lang/libs @rust-lang/lang Should we add RacyUnsafeCell?

#[repr(transparent)]
pub struct RacyUnsafeCell<T>(UnsafeCell<T>);

unsafe impl<T: Sync> Sync for RacyUnsafeCell<T> {}

impl<T> RacyUnsafeCell<T> {
    pub const fn new(x: T) -> Self {
        RacyUnsafeCell(UnsafeCell::new(x))
    }
    pub fn get(&self) -> *mut T {
        self.0.get()
    }
}
@rfcbot

This comment has been minimized.

Copy link

rfcbot commented Aug 23, 2018

Team member @eddyb has asked teams: T-lang, T-libs, for consensus on:

Should we add RacyUnsafeCell?

@eddyb

This comment has been minimized.

Copy link
Member Author

eddyb commented Aug 23, 2018

@RalfJung What we want is &'static Abstraction, I think, and *mut only in methods of it.

That is, the user of the abstraction should be outside of the "unsafe zone" and be able to use only safe code to interact with the abstraction, otherwise it's not really a good abstraction.

@japaric

This comment has been minimized.

Copy link
Member

japaric commented Aug 23, 2018

@eddyb

In embedded we use static mut variables with interrupt handlers. These
handlers are invoked by the hardware and non-reentrant by hardware design (while
executing an interrupt handler the same handler will not be invoked again if
the interrupt signal is received. Also the devices are single core). So we have a
pattern like to let users add state to their interrupt handlers:

// we generate this function using macros
#[export_name = "SOME_KNOWN_NAME_TO_HAVE_THE_LINKER_PUT_THIS_IN_THE_RIGHT_PLACE"]
pub unsafe extern "C" fn interrupt_handler_that_has_some_random_name_unknown_to_the_user() {
    static mut STATE: usize = 0; // initial value provided by the user

    on_button_pressed(&mut STATE);
}

// the user provides this function along with the initial value for the state
fn on_button_pressed(count: &mut usize) {
    *count += 1;
    println!("Button has been pressed {} times", *count);
}

This way the end user indirectly uses static mut variables in a non-reentrant
fashion. The user can't call the interrupt handler themselves because the name
of the function is an implementation detail and if they call on_button_pressed
themselves there's still no problem because that won't operate on the hidden
static mut variable.

As there are no newtypes involved the user can use primitives and types
the define themselves (e.g. structs with pub(crate) fields) without requiring
the unstable const fn feature.

I'm not against this feature as long as the above pattern continues to work on stable Rust 2018.

@scottmcm

This comment has been minimized.

Copy link
Member

scottmcm commented Aug 23, 2018

If there's an API that @japaric and libs are happy with, I'm happy to remove static mut.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Aug 23, 2018

Personally, I think we should just deprecate static mut across the board, but not remove it from Rust 2018. We don't gain much by making it a hard removal, really -- we don't want to "make space" for some other meaning.

@eddyb

This comment has been minimized.

Copy link
Member Author

eddyb commented Aug 23, 2018

@japaric Ah, if you're doing code generation, you can have your own version of RacyUnsafeCell with a public field and no const fn constructor, would that work for you?
UnsafeCell being !Sync isn't necessary, but it does avoid forgetting to impl !Sync.


There's a more powerful pattern I prefer using in those situations:

// Lifetime-invariant ZST token. Probably doesn't even need the invariance.
#[repr(transparent)]
#[derive(Copy, Clone)]
pub struct InterruptsDisabled<'a>(PhantomData<&'a mut &'a ()>);

// Public field type to get around the lack of stable `const fn`.
pub struct InterruptLock<T: ?Sized + Send>(pub UnsafeCell<T>);

// AFAIK this should behave like Mutex.
unsafe impl<T: ?Sized + Send> Sync for InterruptLock<T> {}

impl<T: ?Sized + Send> InterruptLock<T> {
    // This gives access to the `T` that's `Send` but maybe not `!Sync`,
    // for *at most* the duration that the interrupts are disabled for.
    // Note: I wanted to implement `Index` but that can't constrain lifetimes.
    fn get<'a>(&'a self, _: InterruptsDisabled<'a>) -> &'a T {
        unsafe { &*self.0.get() }
    }
}

// Note that you bake any of these into `InterruptLock` without `const fn`,
// because you would need a private field to ensure nobody can touch it.
// OTOH, `UnsafeCell<T>` makes *only constructing* the field public.
pub type InterruptCell<T: ?Sized + Send> = InterruptLock<Cell<T>>;
pub type InterruptRefCell<T: ?Sized + Send> = InterruptLock<RefCell<T>>;
#[export_name = "SOME_KNOWN_NAME_TO_HAVE_THE_LINKER_PUT_THIS_IN_THE_RIGHT_PLACE"]
pub unsafe extern "C" fn interrupt_handler_that_has_some_random_name_unknown_to_the_user(
    token: InterruptsDisabled,
) {
    on_button_pressed(token);
}

static COUNT: InterruptCell<usize> = InterruptLock(UnsafeCell::new(
    Cell::new(0),
));
fn on_button_pressed(token: InterruptsDisabled) {
    let count = COUNT.get(token);
    count.set(count.get() + 1);
    println!("Button has been pressed {} times", count.get());
}

There's another variation on this, where you make the token non-Copy and can do:

#[export_name = "SOME_KNOWN_NAME_TO_HAVE_THE_LINKER_PUT_THIS_IN_THE_RIGHT_PLACE"]
pub unsafe extern "C" fn interrupt_handler_that_has_some_random_name_unknown_to_the_user(
    token: InterruptsDisabled,
) {
    on_button_pressed(token);
}

static COUNT: InterruptLock<usize> = InterruptLock(UnsafeCell::new(
    0,
));
fn on_button_pressed(mut token: InterruptsDisabled) {
    // This mutable borrow of `token` prevents using it for
    // accessing any `InterruptLock`s, including `COUNT` itself.
    // The previous version was like `&token` from this one.
    let count = COUNT.get_mut(&mut token);
    *count += 1;
    println!("Button has been pressed {} times", *count);
}

(I didn't write the implementation details for the second version for brevity's sake, if desired I can edit this later)

You can even allow creating a InterruptsDisabled out of a &mut InterruptsDisabled, effectively sub-borrowing it (assuming you want to keep it ZST so it stays zero-cost).
Then you can combine the two versions by allowing the creation of the first version from a &InterruptsDisabled from the second version (but, again, only to keep it ZST).

Theoretically you can even create a InterruptsDisabled using HRTB and calling a closure after disabling interrupts, but this only works for the first version, not the second, stronger, one.
Also, IIRC, on some platforms it's impossible to truly turn all interrupts off.


IMO a safe abstraction like this is a good long-term investment, since it can handle any sort of Send + !Sync data that's accessed only from within interrupts.

cc @RalfJung Has anything like this been proven safe?

@Nemo157

This comment has been minimized.

Copy link
Contributor

Nemo157 commented Aug 23, 2018

@eddyb note that the non-reentrancy that @japaric describes is for that particular interrupt handler (and any lower priority, but that’s hard to do anything useful with). Interrupts are not globally disabled during an interrupt handler on architectures like ARMv6-M. It might be possible to use a more complicated token scheme where each interrupt has its own token type, but I don’t know if anyone has looked into some way to make that actually usable.

@eddyb

This comment has been minimized.

Copy link
Member Author

eddyb commented Aug 23, 2018

@Nemo157 Ah, that's a very interesting detail! Yeah you can just have one token type per level and use From impls to connect them up or something (and the methods in InterruptLock<Level, T> would take impl Into<InterruptsDisabled<Level, 'a>> instead).


Another idea that I came up with while discussing with @arielb1 on IRC:

We could only allow private static mut. So you can keep it e.g. in a module, or within a block inside a fn, const, or another static, etc. - but not export it out of there.

That, combined with deprecation of private static mut, could improve the situation.

@shepmaster

This comment has been minimized.

Copy link
Member

shepmaster commented Aug 26, 2018

In embedded we use static mut variables with interrupt handlers. These
handlers are invoked by the hardware and non-reentrant by hardware design

IIRC, AVR actually allows for recursive interrupt handlers:

#define ISR_NOBLOCK
ISR runs with global interrupts initially enabled. The interrupt enable flag is activated by the compiler as early as possible within the ISR to ensure minimal processing delay for nested interrupts.

This may be used to create nested ISRs, however care should be taken to avoid stack overflows, or to avoid infinitely entering the ISR for those cases where the AVR hardware does not clear the respective interrupt flag before entering the ISR.

Use this attribute in the attributes parameter of the ISR macro.

It's up to the programmer to choose (and thus appropriately handle) this case.

@whitequark

This comment has been minimized.

Copy link
Member

whitequark commented Aug 26, 2018

IIRC, AVR actually allows for recursive interrupt handlers:

On every architecture I'm aware of, you can explicitly set the interrupt flag after entering a handler; this is not AVR-specific. (On architectures with interrupt prioritization, like Cortex-M, you'll also need to manipulate priorities.) But the point here is that this needs to be done explicitly, with unsafe code; the architecture guarantees that you wouldn't reenter the ISR if you don't specifically request that.

So this is perfectly in line with the usual safety rules.

@gnzlbg

This comment has been minimized.

Copy link
Contributor

gnzlbg commented Oct 24, 2018

Some C APIs expose mutable globals that my Rust code right now access via:

extern "C" {
    pub static mut FOO: BAR;
}

Some C APIs require their users to define mutable globals, that the C library then accesses. Right now my Rust code interfaces with those using:

#[export(name = "foo")]
pub static mut FOO: BAR = ..initializer..;

How do I access mutable globals from C libraries, and how do I provide mutable globals to C libraries, without static mut ?

An example of a C library that uses both is jemalloc.

@alexreg

This comment has been minimized.

Copy link
Contributor

alexreg commented Oct 24, 2018

I don’t like this. It’s already unsafe. That’s good enough.

@Centril

This comment has been minimized.

Copy link
Contributor

Centril commented Oct 25, 2018

@RalfJung @alercah @gnzlbg would you perhaps be willing to make this actionable by writing an RFC with an "alternatives + deprecation in 2018 -> hard error in Edition.Next"-plan which also takes into account @japaric's concerns? Maybe y'all can collaborate on this?

@RalfJung

This comment has been minimized.

Copy link
Member

RalfJung commented Oct 25, 2018

I got two other RFCs I still want to write that I care about more strongly, so I don't think I'd get to this one any time soon -- sorry.

@alercah

This comment has been minimized.

Copy link
Contributor

alercah commented Oct 25, 2018

I also don't have time for this, unfortunately.

@emilio

This comment has been minimized.

Copy link
Contributor

emilio commented Oct 26, 2018

I don't see the point of doing this since all the accesses are unsafe by default. I don't know what the FFI story is for this is but please do consider it :)

@alexreg

This comment has been minimized.

Copy link
Contributor

alexreg commented Oct 26, 2018

I'd like to see a demonstration or at the very least hand-wavy explanation that the proposed generic replacement for static mut has equivalent-or-better performance characteristics. This is essential for any proposal to deprecate a feature.

@alercah

This comment has been minimized.

Copy link
Contributor

alercah commented Oct 26, 2018

Take a look at the IR for this example. I used FFI functions to prevent optimization of the loads and stores away. If you look at the IR, you'll see that both stores to the statics are optimized down to a single store instruction.

It's not a comprehensive proof, of course, but it demonstrates that the optimizer can figure this out.

@alexreg

This comment has been minimized.

Copy link
Contributor

alexreg commented Oct 26, 2018

@alercah Thanks. That's good to know. I'm somewhat less opposed to the proposal now, especially if this optimisation holds on all platforms... even though there still exist other reasons for maintaining it (namely uniformity and the fact has already opened the door to UB by using unsafe, so why bother).

@SimonSapin

This comment has been minimized.

Copy link
Contributor

SimonSapin commented Nov 2, 2018

#55207 (comment) proposes redefining UnsafeCell to be based on a new type that would be Copy (and become the lang item). That same type could also be Sync and play the role of RacyUnsafeCell.

@eddyb

This comment has been minimized.

Copy link
Member Author

eddyb commented Nov 2, 2018

@alexreg UnsafeCell is supposed to be a zero-cost abstraction, to the point where any observable difference between T and UnsafeCell<T> is an outright codegen bug.

As for FFI, UnsafeCell and RacyUnsafeCell should be #[repr(transparent)], meaning that extern { static X: RacyUnsafeCell<T>; } should work as well as extern { static mut X: T }.

And statics use memory ABI anyway, so I think you could also use a racy version of Cell, or sync::Atomic{Usize,Ptr,...} types, to access FFI statics.


Anyway, we're not doing this in Rust 2018, and I agree with suggestions of general deprecation.

@eddyb eddyb changed the title Consider removing UB-happy `static mut` in Rust 2018. Consider removing UB-happy `static mut` in a future edition. Nov 2, 2018

@alexreg

This comment has been minimized.

Copy link
Contributor

alexreg commented Nov 2, 2018

@eddyb Okay, fair enough then!

@Centril

This comment has been minimized.

Copy link
Contributor

Centril commented Dec 2, 2018

@eddyb asked me to note that const fn as of 1.31 will permit constructing structs with private fields where the struct is without constraints and where you don't impose additional constraints on the impl itself either.

@SimonSapin

This comment has been minimized.

Copy link
Contributor

SimonSapin commented Dec 2, 2018

@Centril I don’t understand what that means or how it relates to min_const_fn

@Centril

This comment has been minimized.

Copy link
Contributor

Centril commented Dec 2, 2018

@SimonSapin I'm not sure either; but apparently private struct fields in const fns were a blocker wrt. deprecating static mut somehow; and on beta you can do:

struct Foo {
    field: u8,
}

impl Foo {
    const fn bar() -> Self {
        Self { field: 0 }
    }
}

which apparently unblocks something? @eddyb can elaborate perhaps.

@eddyb

This comment has been minimized.

Copy link
Member Author

eddyb commented Dec 2, 2018

One of the main points of discussion on this issue has been the lack of stable const fn which limits the ability to write constructors for abstractions to be used with statics (e.g. #53639 (comment)).

@RalfJung

This comment has been minimized.

Copy link
Member

RalfJung commented Feb 10, 2019

Instead of killing static mut entirely, what about just disallowing creating mutable references to them? The footgun here is accidentally creating a &'static mut.

With rust-lang/rfcs#2582, we can tell people that &mut STATIC_MUT as *mut _ is a way to create a raw pointer to a static mut, and we could even make that a safe operation. We could then deprecate creating mutable references that are not directly turned into raw pointers. Because static mut might be mutated at any time, it is probably a good idea to apply the same rule for shared references.

@retep998

This comment has been minimized.

Copy link
Member

retep998 commented Feb 10, 2019

That still doesn't solve the footgun of any sort of concurrent modification of a static mut being UB because they're not atomic. Or even with just a single thread, creating a &T and then modifying the original static mut.

@gnzlbg

This comment has been minimized.

Copy link
Contributor

gnzlbg commented Feb 10, 2019

@retep998 Neither does removing static mut and recommending people to use RacyUnsafeCell<T> instead.

@RalfJung

This comment has been minimized.

Copy link
Member

RalfJung commented Feb 10, 2019

@retep998 note that creating a &T requires going through a raw pointer.

@eddyb

This comment has been minimized.

Copy link
Member Author

eddyb commented Feb 10, 2019

@gnzlbg But RacyUnsafeCell<T> only lets you do the equivalent of &mut FOO as *mut T, without any references, mutable or immutable, to T, so it's at least as safe as a take-raw-pointer-only static mut - or is that your point?

One advantage is that a value of type &'static RacyUnsafeCell<T> is safer than a *mut T, as it's guaranteed to be a valid reference to an interior-mutable T, so the duration for which you interact with the T through *mut T tends to be shorter.

The much bigger advantage is that RacyUnsafeCell is just a stopgap solution, where the correct approach (now possible on stable in some cases because of const fn stabilization!) is to build a completely safe API around the T (rarely is this actually impossible, and if it is impossible, you should seriously reconsider whether what you're doing is sound at all).

@gnzlbg

This comment has been minimized.

Copy link
Contributor

gnzlbg commented Feb 11, 2019

@eddyb I was only referring to the issue that @retep998 mentioned. Just using RacyUnsafeCell does not really remove the footgun of potential data-races when mutating a static (one needs synchronization for that).

As you mention, using a RacyUnsafeCell does help with other issues that &mut T as *mut T does not address.

BurntSushi added a commit to BurntSushi/rust-memchr that referenced this issue Feb 12, 2019

ifunc: use static with AtomicPtr
This replaces the use of `static mut`, because there's a faint rumbling
in the distance that static mut is to be avoided[1]. It seems to be a
problem when we take *mutable* references to the static, and this code
didn't. Therefore, this is only done out of an abundance of caution;
there is no belief that the previous code was incorrect.

Still, we port the code to using a regular static with an atomic.
We can fortunately use a fn to raw pointer cast (fn to usize is not
available in statics).

Closes #42

[1] - rust-lang/rust#53639

BurntSushi added a commit to BurntSushi/rust-memchr that referenced this issue Feb 12, 2019

ifunc: use static with AtomicPtr
This replaces the use of `static mut`, because there's a faint rumbling
in the distance that static mut is to be avoided[1]. It seems to be a
problem when we take *mutable* references to the static, and this code
didn't. Therefore, this is only done out of an abundance of caution;
there is no belief that the previous code was incorrect.

Still, we port the code to using a regular static with an atomic.
We can fortunately use a fn to raw pointer cast (fn to usize is not
available in statics).

Closes #42

[1] - rust-lang/rust#53639
@RReverser

This comment has been minimized.

Copy link
Contributor

RReverser commented Mar 17, 2019

Or even with just a single thread, creating a &T and then modifying the original static mut.

@retep998 This is the case I've seen too.

note that creating a &T requires going through a raw pointer.

@RalfJung Do you mean in the current state of things or in proposed one? Currently it doesn't (although does require unsafe).

For example, if we take following Rust code:

pub fn f() -> u8 {
    let mut x = 10;
    
    let x_ref = &x;

    x = 20;
    
    *x_ref
}

Then, as expected, we get a borrow checker error due to mutation of variable while it's still borrowed immutably:

error[E0506]: cannot assign to `x` because it is borrowed
 --> <source>:6:5
  |
4 |     let x_ref = &x;
  |                  - borrow of `x` occurs here
5 | 
6 |     x = 20;
  |     ^^^^^^ assignment to borrowed `x` occurs here

But, if we use static mut, we can do:

pub unsafe fn g() -> u8 {
    static mut X: u8 = 10;

    let x_ref = &X;

    X = 20;
    
    *x_ref
}

without any errors and raw pointers involved, despite mutating an already-borrowed variable.

@RalfJung

This comment has been minimized.

Copy link
Member

RalfJung commented Mar 18, 2019

Do you mean in the current state of things or in proposed one? Currently it doesn't (although does require unsafe).

I meant with my proposal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.