Why is transmuting raw pointers considered safe?

This is safe:

fn foo<T, U>(x: &T) -> *mut U {
    x as *const T as *mut U

Unfortunately this (among other safe-raw-pointers things) prevents making e.g. Atomic* take *const instead of &. Which sucks, as raw pointers have the property of not enforcing the properties of references at the LLVM IR level and whatnot, and this would be nice sometimes, even in safe APIs. What's the rationale for transmuting raw pointers being safe and whatnot? To us it seems like this feature (raw pointers) is best described as an oversight or a mistake since safe code should never be interacting with raw pointers in the first place. But maybe it exists this way to actively prevent safe abstractions from using raw pointers? We can't find anything about it tho. Otherwise the implicit conversion between & and *const would've been nice to abuse in some APIs.

What about making it so raw pointers are entirely unsafe in 2021 edition? Specifically:

  1. Reading a raw pointer is unsafe.
  2. Writing a raw pointer is safe.
  3. Transmuting raw pointers, casting raw pointers, etc, is unsafe.

This would allow one to pass &Foo straight to *const Foo, it'd also be sound because altho you can store *const Foo like in let x: *const Foo = &Foo, you can't actually read it without unsafe, so something like:

let x = Foo;
let y: *const Foo = &x;
bar(y); // error: reading raw pointers is unsafe

just doesn't compile. Returned raw pointers also count as reading so bar(ptr::null()) would be automatically disallowed in safe code without even changing the definition of ptr::null(). So what else would change is that for pre-2021 edition code, the post-2021 edition safe functions (except those marked with some internal rustc attribute, for backwards compatibility) that take raw pointers would seem as if they take references-only instead.

With transmuting raw pointers, what's the problem? It's the dereference that causes issues, and that's already unsafe. Having multiple *mut pointers to the same object isn't (as far as I know) in and of itself an issue — it's multiple &mut references that is.


Can you please explain the relevance of those links? At quick glance, I see nothing relevant to what you're saying.

1 Like

You wouldn't need to duplicate the API surface of Atomic* if raw pointers played nicer with safety - you'd just make all those &Self into *const Self and call it a day.

There seems to a common theme in many of your proposals: proposing massively breaking changes, without any explicit consideration of the impact this will have on Rust crates. Putting this behind the 2021 edition doesn't solve the issue - the intention is that (maintained) crates will eventually migrate to the 2021 edition, so you're at best just delaying the massive amount of code churn that this would require.

For reference, the obscure unix-only function CommandExt::before_exec was deprecated instead of being made unsafe, in order to avoid breaking changes. You're proposing making a fundamental language operation (casting references to pointers via as) into an unsafe operation, along with every single function in the standard library that produces a raw pointer.


Not making them unsafe, just making their usage unsafe in a new language edition. It maintains the stability guarantees so it's fine.

If you don't wanna deal with it just stick to 2015 or 2018 edition.

Editions don't give you carte blanche to change anything you want, though. Major changes such as this one probably would be a nonstarter, but I'm not on the lang team.

If you don't wanna deal with it just stick to 2015 or 2018 edition.

That is a terrible suggestion. The goal is for everyone to upgrade to 2021+ eventually.

I'm still not entirely sure how what you're proposing would solve the API duplication that is internal anyways, to be honest.


That's a meaningless distinction. Running cargo new creates a crate using the latest edition, and it's intended that all (maintained) crates will eventually switch to the latest edition. Making something unsafe in a certain editiod would mean that we would eventually like all crates to consider it unsafe.

A key part of editions is that crates in a dependency graph can be using different editions. This is fundamentally in conflict with the idea of unsafe, which is used to mark code that may lead to undefined behavior. What undefined behavior can be caused by casting a reference to a raw pointer? How is it sound for some crates in the ecosystem to continue to treat this as a safe operation, if it can lead to undefined behavior?


It is external tho. The point is that Atomic* would provide unsafe fn raw_* that take raw pointers as well as the safe fn we have today. The former would be used by unsafe code to make e.g. Arc work properly. But there's another way: if raw pointers worked properly (lol) then you could just change the existing safe API to take raw pointers. but you can't do that in current rust because that's not how raw pointers work unfortunately.

It's fine because safe APIs that take raw pointers wouldn't be exposed to pre-2021 edition crates as taking raw pointers. So even tho operations on raw pointers are unsafe, and pre-2021 treats them as safe, you wouldn't be able to use any of the resulting "safe" raw pointers.

No, you couldn't. Raw pointers don't make any guarantees about the validity of the memory they point to - that's the entire reason why they exist. Even if you somehow made it unsafe to produce a raw pointer through any means, there is no guarantee that a raw pointer will remain valid forever. Any function that dereferences an arbitrary caller-provided raw pointer must be unsafe, to reflect the fact that the caller must ensure that the raw pointer can be validly referenced.


These are the four things you said that are the core of your proposal. Raw pointers are no more than an integer with no semantic meaning whatsoever. They are fundamentally different than references.

I do not understand why you want to make (3) unsafe, and you've provided no explanation as to why it should be. (1) is already the case, and (2) I presume you mean construction...which again is already the case.

Can you please provide a clear, succinct explanation of what the supposed undefined behavior is that you're trying to correct by making such a massive change?

1 Like

That's where reading a raw pointer being unsafe comes in. You can't pass in dangling or aliased raw pointers to functions without using unsafe because you can't read them.

On the other hand this is precisely what you want for the Atomic APIs. It would drop the dereferencable attribute on fetch_add, since it provides none of the guarantees, thus solving the problem.

This would be a significant change to the compiler, requiring editions to interact with method resolution and type checking.

Also, this does nothing to solve the problem of undefined behavior. Dereferencing a raw pointer is unsafe because undefined behavior can occur in many different ways (the pointer is null, unaligned, causes a data race, etc.). This is true regardless of the edition of a crate, because Rust assumes that undefined behavior never happens. What undefined behavior can be caused by casting a reference to a raw pointer?

1 Like

If calling the function with a raw pointer is already unsafe, then what do we gain by making it unsafe to construct a raw pointer?

1 Like

The point is that calling fn fetch_add(*const self, ...) should be safe, as long as it's done on a reference ("writing a pointer (itself, not the value it points to) is safe, reading one is unsafe"). E.g. (foo: &AtomicUsize).fetch_add(...) would work and be safe, but (foo: *const AtomicUsize).fetch_add(...) would error because attempt to read pointer outside of unsafe context.

The body of the fetch_add function has no way of knowing if it was 'originally' invoke with a reference or raw pointer. Since fetch_add needs to dereference a raw pointer, it needs to use an unsafe block. How can fetch_add guarantee that this unsafe block is sound, if it doesn't know anything about the raw pointer that was passed in?


Because the other rules would require the use of an unsafe block to pass in a non-reference raw pointer in the first place.

So you're proposing using unsafe for somewhere unsafety doesn't exist?