Skip to content

Allow UnsafeCell in shared statics#152540

Open
madsmtm wants to merge 2 commits intorust-lang:mainfrom
madsmtm:unsafecell-shared-static
Open

Allow UnsafeCell in shared statics#152540
madsmtm wants to merge 2 commits intorust-lang:mainfrom
madsmtm:unsafecell-shared-static

Conversation

@madsmtm
Copy link
Contributor

@madsmtm madsmtm commented Feb 12, 2026

Background

Using a type in a (shared) static introduces a Sync bound on that type, to ensure that it is safe to share across threads.

This is sufficient, but overly restrictive: there exist other types which are safe to use directly in a static, including UnsafeCell and raw pointers (these types are !Sync primarily as a "hint" to avoid items containing them being Sync via auto traits, but they are by themselves not unsafe to share across multiple threads).

For example static NULL_INT: *const i32 = std::ptr::null(); would be perfectly valid. Another (very contrived) example can be found in this playground.

Proposal

I propose special-casing UnsafeCell<T: Sync> to allow it as the type in shared statics.

This would enable the following to compile:

static FOO: UnsafeCell<i32> = UnsafeCell::new(42);

fn main() {
    // SAFETY: No other threads are accessing FOO.
    unsafe { FOO.get().write(43) };
}

As an alternative to:

static mut FOO: i32 = 42;

fn main() {
    // SAFETY: No other threads are accessing FOO.
    unsafe { addr_of_mut!(FOO).write(43) };
}

While the following would still be an error (because MyCell is !Sync):

struct MyCell(UnsafeCell<i32>);
static CELL: MyCell = MyCell(UnsafeCell::new(42));

Unlike adding an unsafe impl<T: Sync> Sync for UnsafeCell<T>, this would not be a breaking change as far as I can tell, as library authors shouldn't rely on an arbitrary UnsafeCell not being shared elsewhere (APIs taking &UnsafeCell<T> can't be safe).

This is intended as a partial alternative to SyncUnsafeCell (tracking issue), which was introduced primarily to avoid having to use static mut, and secondarily to avoid the need for a manual Sync impl. (This PR would make the first item redundant but wouldn't change the second).

I have opened rust-lang/reference#2169 to update the reference, to allow you to see how things would look if this was stabilized.

Implementation

This PR introduces a new unstable trait AllowSharedStatic which is now used for the bound on shared statics instead of Sync. It is defined as:

#[lang = "allow_shared_static"]
pub unsafe trait AllowSharedStatic {}
unsafe impl<T: MetaSized + Sync> AllowSharedStatic for T {}
unsafe impl<T: MetaSized + Sync> AllowSharedStatic for UnsafeCell<T> {}

This trait is intended to be perma-unstable and not be exposed to users (at least not unless we find a compelling reason to do so). The trait could be implemented for other types in the future (such as *const T/*mut T or UnsafeCell<T: !Sync>).

TODO

  • Make this opt-in with an unstable feature, and create a tracking issue.
  • Measure performance impact, special-case in compiler if necessary.
  • Better diagnostics, we (probably?) want to keep using Sync's "on_unimplemented" messages, and AllowSharedStatic shouldn't appear in error messages.
  • Proper documentation.
  • Bikeshed naming.

I will do these if t-lang decides to go forwards with this idea.
r? compiler

This is a new trait that is used by the language in bounds for `static`
items instead of `Sync`. It is implemented by all `T: Sync`, as well as
by `UnsafeCell<T: Sync>`.
The only place where it was required by the language was for checking
usage in static variables (and that is now done by `AllowSharedStatic`),
all other usage in the compiler was for diagnostics.
@madsmtm madsmtm added T-lang Relevant to the language team T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Feb 12, 2026
@rustbot
Copy link
Collaborator

rustbot commented Feb 12, 2026

Some changes occurred in src/tools/clippy

cc @rust-lang/clippy

This PR modifies tests/auxiliary/minicore.rs.

cc @jieyouxu

Some changes occurred in compiler/rustc_codegen_gcc

cc @antoyo, @GuillaumeGomez

Some changes occurred in compiler/rustc_codegen_cranelift

cc @bjorn3

rust-analyzer is developed in its own repository. If possible, consider making this change to rust-lang/rust-analyzer instead.

cc @rust-lang/rust-analyzer

@rustbot rustbot added A-run-make Area: port run-make Makefiles to rmake.rs A-test-infra-minicore Area: `minicore` test auxiliary and `//@ add-core-stubs` S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Feb 12, 2026
@rustbot rustbot added T-clippy Relevant to the Clippy team. T-rust-analyzer Relevant to the rust-analyzer team, which will review and decide on the PR/issue. labels Feb 12, 2026
@madsmtm
Copy link
Contributor Author

madsmtm commented Feb 12, 2026

@rustbot label +I-lang-nominated

Nominating for t-lang:

Do you want to preserve the "shared static" == "requires Sync" relationship, or would you be open to extending that to UnsafeCell<T: Sync> as well (which is !Sync)?

This would allow writing static FOO: UnsafeCell<T: Sync>, and resolve a lot of the use-cases for SyncUnsafeCell.

I guess part of the motivation for this (and SyncUnsafeCell) is to avoid static mut, so an alternative here would also be to recommend static mut more strongly as "this is how you're supposed to use the language".

@rustbot rustbot added the I-lang-nominated Nominated for discussion during a lang team meeting. label Feb 12, 2026
@madsmtm madsmtm added S-waiting-on-t-lang Status: Awaiting decision from T-lang and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Feb 12, 2026
@rust-log-analyzer
Copy link
Collaborator

The job aarch64-gnu-llvm-20-2 failed! Check out the build log: (web) (plain enhanced) (plain)

Click to see the possible cause of the failure (guessed by this bot)
REPOSITORY                                   TAG       IMAGE ID       CREATED       SIZE
ghcr.io/dependabot/dependabot-updater-core   latest    afc745c7535d   2 weeks ago   783MB
=> Removing docker images...
Deleted Images:
untagged: ghcr.io/dependabot/dependabot-updater-core:latest
untagged: ghcr.io/dependabot/dependabot-updater-core@sha256:faae3d3a1dedd24cde388bb506bbacc0f7ed1eae99ebac129af66acd8540c84a
deleted: sha256:afc745c7535da1bb12f92c273b9a7e9c52c3f12c5873714b2542da259c6d9769
deleted: sha256:64e147d5e54d9be8b8aa322e511cda02296eda4b8b8d063c6a314833aca50e29
deleted: sha256:5cba409bb463f4e7fa1a19f695450170422582c1bc7c0e934d893b4e5f558bc6
deleted: sha256:cddc6ebd344b0111eaab170ead1dfda24acdfe865ed8a12599a34d338fa8e28b
deleted: sha256:2412c3f334d79134573cd45e657fb6cc0abd75bef3881458b0d498d936545c8d
---
fmt: checked 6701 files
tidy check
tidy [rustdoc_json (src)]: `rustdoc-json-types` modified, checking format version
tidy: Skipping binary file check, read-only filesystem
tidy [style (compiler)]: /checkout/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs:3403: TODO is used for tasks that should be done before merging a PR; If you want to leave a message in the codebase use FIXME
tidy [style (library)]: /checkout/library/core/src/marker.rs:680: TODO is used for tasks that should be done before merging a PR; If you want to leave a message in the codebase use FIXME
tidy [style (library)]: /checkout/library/core/src/marker.rs:703: TODO is used for tasks that should be done before merging a PR; If you want to leave a message in the codebase use FIXME
tidy [style (library)]: FAIL
tidy [style (compiler)]: FAIL
tidy: The following checks failed: style (compiler), style (library)
Bootstrap failed while executing `--stage 2 test --skip tests --skip coverage-map --skip coverage-run --skip library --skip tidyselftest`
Command `/checkout/obj/build/aarch64-unknown-linux-gnu/stage1-tools-bin/rust-tidy --root-path=/checkout --cargo-path=/checkout/obj/build/aarch64-unknown-linux-gnu/stage0/bin/cargo --output-dir=/checkout/obj/build --concurrency=4 --npm-path=yarn` failed with exit code 1
Created at: src/bootstrap/src/core/build_steps/tool.rs:1612:23
Executed at: src/bootstrap/src/core/build_steps/test.rs:1365:29

Command has failed. Rerun with -v to see more details.
Build completed unsuccessfully in 0:00:50
  local time: Thu Feb 12 15:32:49 UTC 2026
  network time: Thu, 12 Feb 2026 15:32:50 GMT
##[error]Process completed with exit code 1.
##[group]Run echo "disk usage:"

@madsmtm madsmtm removed A-run-make Area: port run-make Makefiles to rmake.rs T-rust-analyzer Relevant to the rust-analyzer team, which will review and decide on the PR/issue. A-test-infra-minicore Area: `minicore` test auxiliary and `//@ add-core-stubs` T-clippy Relevant to the Clippy team. labels Feb 12, 2026
//
// See also TODO link.
#[unstable(feature = "allow_shared_static_trait", issue = "none")]
unsafe impl<T: MetaSized + Sync> AllowSharedStatic for UnsafeCell<T> {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this have a Sync bound on T? The argument for why this is sound (all accesses are unsafe) also work for arbitrary T.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I intended for this PR to be the minimal first step. We can extend it to T: !Sync later (or during the unstable period).

@ChayimFriedman2
Copy link
Contributor

What is the advantage of this over the much simpler SyncUnsafeCell?

@madsmtm
Copy link
Contributor Author

madsmtm commented Feb 12, 2026

What is the advantage of this over the much simpler SyncUnsafeCell?

Simple is in the eye of the beholder.

To me, SyncUnsafeCell feels like a hack that exists because we want UnsafeCell to disable the automatic Sync impl on structs containing it (or we at least have to keep it that way for backwards compat). In a different world where Sync was not an auto trait, SyncUnsafeCell would not exist.

You could argue this PR is a hack too, but then it's just a choice between the least ugly hack ;)

(It's also not clear to me from just the name whether SyncUnsafeCell<*const i32> would be Sync or not).


As a more general problem, bifurcation of types like this is not without a cost, users are gonna have to learn this new type and crates in the ecosystem like zerocopy that wants to interoperate well with the standard library has to be updated to handle it.

@ChayimFriedman2
Copy link
Contributor

I claim that this PR is way more ugly hack and in fact, SyncUnsafeCell is not a hack at all. You're changing the language here, while SyncUnsafeCell is just a standard library type anyone can write and understand. The bar for language changes is much higher.

@RalfJung
Copy link
Member

RalfJung commented Feb 12, 2026

I think the ideal solution would be a language change that lets me unsafely say "yes I want this static with a !Sync type, just let me do it and make accesses unsafe". But that's probably a bit too heavy of a hammer. I like the idea of using the existing UnsafeCell instead of proliferating yet another wrapper type.

@RalfJung
Copy link
Member

That said -- I also like just using static mut for this. I am not yet entirely convinced there even is a problem left to solve here.

@madsmtm
Copy link
Contributor Author

madsmtm commented Feb 12, 2026

That said -- I also like just using static mut for this. I am not yet entirely convinced there even is a problem left to solve here.

Well, me neither, but I guess that's part of what I want to help figure out.

@traviscross traviscross added the P-lang-drag-1 Lang team prioritization drag level 1. https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang label Feb 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

I-lang-nominated Nominated for discussion during a lang team meeting. P-lang-drag-1 Lang team prioritization drag level 1. https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang S-waiting-on-t-lang Status: Awaiting decision from T-lang T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants