Skip to content

Commit 6a803f1

Browse files
committed
move SUGAR to its own subsection under future possibilities
1 parent 9889619 commit 6a803f1

File tree

1 file changed

+41
-50
lines changed

1 file changed

+41
-50
lines changed

text/0000-raw-reference-mir-operator.md

Lines changed: 41 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@
99
Introduce new variants of the `&` operator: `&raw mut <place>` to create a `*mut <T>`, and `&raw const <place>` to create a `*const <T>`.
1010
This creates a raw pointer directly, as opposed to the already existing `&mut <place> as *mut _`/`&<place> as *const _`, which create a temporary reference and then cast that to a raw pointer.
1111
As a consequence, the existing expressions `<term> as *mut <T>` and `<term> as *const <T>` where `<term>` has reference type are equivalent to `&raw mut *<term>` and `&raw const *<term>`, respectively.
12-
Moreover, add a lint to existing code that could use the new operator, and treat existing code that creates a reference and immediately casts or coerces it to a raw pointer as if it had been written with the new syntax.
13-
14-
As an option (referred to as [SUGAR] below), we could treat `&mut <place> as *mut _`/`&<place> as *const _` as if they had been written with `&raw` to avoid creating temporary references when that was likely not the intention.
12+
Moreover, add a lint to existing code that could use the new operator.
1513

1614
# Motivation
1715
[motivation]: #motivation
@@ -79,8 +77,7 @@ let &x = &X; // this is actually dereferencing the pointer, certainly UB
7977
let _ = &X; // throwing away the value immediately changes nothing
8078
&X; // different syntax for the same thing
8179

82-
let x = &X as *const T; // this is casting to raw but "too late", an intermediate reference has been created (only if we do no adapt [SUGAR])
83-
let x = &X as &T as *const T; // this is casting to raw but "too late" even if we adapt [SUGAR]
80+
let x = &X as *const T; // this is casting to raw but "too late", an intermediate reference has been created
8481
```
8582

8683
The only way to create a pointer to an unaligned or dangling location without triggering undefined behavior is to use `&raw`, which creates a raw pointer without an intermediate reference.
@@ -90,19 +87,6 @@ The following is valid:
9087
let packed_cast = &raw const packed.field;
9188
```
9289

93-
As an optional extension ([SUGAR]) to keep existing code working and to provide a way for projects to adjust to these rules before the syntax bikeshed is finished, and to do so in a way that they do not have to drop support for old Rust versions, we could also treat all of the following as if they had been written using `&raw const` instead of `&`:
94-
95-
```rust
96-
let packed_cast = unsafe { &raw const packed.field as *const _ };
97-
let packed_coercion: *const _ = unsafe { &packed.field };
98-
let null_cast: *const _ = unsafe { &*ptr::null() } as *const _;
99-
let null_coercion: *const _ = unsafe { &*ptr::null() };
100-
```
101-
102-
The intention is to cover all cases where a reference, just created, is immediately explicitly used as a value of raw pointer type.
103-
104-
Notice that this only applies if no automatic call to `deref` or `deref_mut` got inserted:
105-
those are regular function calls taking a reference, so in that case a reference is created and it must satisfy the usual guarantees.
10690

10791

10892
# Reference-level explanation
@@ -114,11 +98,6 @@ The borrow checker should do the usual checks on the place used in `&raw`, but c
11498
When translating MIR to LLVM, nothing special has to happen as references and raw pointers have the same LLVM type anyway; the new operation behaves like `Ref`.
11599
When interpreting MIR in the Miri engine, the engine will know not to enforce any invariants on the raw pointer created by `&raw`.
116100

117-
For the [SUGAR] option, when translating HIR to MIR, we recognize `&[mut] <place> as *[mut|const] ?T` (where `?T` can be any type, also a partial one like `_`) as well as coercions from `&[mut] <place>` to a raw pointer type as a special pattern and treat them as if they would have been written `&raw [mut|const] <place>`.
118-
We do this *after* auto-deref, meaning this pattern does not apply when a call to `deref` or `deref_mut` got inserted.
119-
Redundant parentheses are ignored, but block expressions are not:
120-
`{ &[mut] <place> }` materializes a reference that must be valid, no matter which coercions or casts follow outside the block.
121-
122101
When doing unsafety checking, we make references to packed fields that do *not* use this new "raw reference" operation a *hard error even in unsafe blocks* (after a transition period).
123102
There is no situation in which this code is okay; it creates a reference that violates basic invariants.
124103
Taking a raw reference to a packed field, on the other hand, is a safe operation as the raw pointer comes with no special promises.
@@ -128,35 +107,16 @@ This check has nothing to do with whether we are in an unsafe block or not.
128107
Moreover, to prevent programmers from accidentally creating a safe reference when they did not want to, we add a lint that identifies situations where the programmer likely wants a raw reference, and suggest an explicit cast in that case.
129108
One possible heuristic here would be: If a safe reference (shared or mutable) is only ever used to create raw pointers, then likely it could be a raw pointer to begin with.
130109
The details of this are best worked out in the implementation phase of this RFC.
131-
The lint should, at the very least, fire for the cases covered by [SUGAR] if we do *not* adopt that option, and it should fire when the factor that prevents this matching [SUGAR] is just a redundant block, such as `{ &mut <place> } as *mut ?T`.
110+
The lint should, at the very least, fire for the cases covered by the syntactic sugar extension (see [Future possibilities][future-possibilities]), and it should fire when the factor that prevents this matching the sugar is just a redundant block, such as `{ &mut <place> } as *mut ?T`.
132111

133112
# Drawbacks
134113
[drawbacks]: #drawbacks
135114

136-
If we adapt [SUGAR], it might be surprising that the following two pieces of code are not equivalent:
137-
138-
```rust
139-
// Variant 1
140-
let x = unsafe { &packed.field }; // Undefined behavior!
141-
let x = x as *const _;
142-
// Variant 2
143-
let x = unsafe { &packed.field as *const _ };
144-
```
145-
146-
Notice, however, that the lint should fire in variant 1.
147-
148-
If `as` ever becomes an operation that can be overloaded, the behavior of `&packed.field as *const _` with [SUGAR] can *not* be obtained by dispatching to the overloaded `as` operator.
149-
Calling that method would assert validity of the reference.
115+
This introduces new clauses into our grammar for a niche operation.
150116

151117
# Rationale and alternatives
152118
[rationale-and-alternatives]: #rationale-and-alternatives
153119

154-
[SUGAR] is a compromise to keep the analysis that affects code generation simple.
155-
Detecting "Variant 1" (from the "Drawbacks" section) would need a much less local analysis.
156-
Hence the proposal to make them not equivalent.
157-
158-
A drawback of not adapting [SUGAR] is that we will have to wait longer (namely, until stabilization of the new syntax) until people can finally write UB-free versions of code that handles dangling or unaligned raw pointers.
159-
160120
One alternative to introducing a new primitive operation might be to somehow exempt "references immediately cast to a raw pointer" from the invariant.
161121
(Basically, a "dynamic" version of the static analysis performed by the lint.)
162122
However, I believe that the semantics of a MIR program, including whether it as undefined behavior, should be deducible by executing it one step at a time.
@@ -171,11 +131,6 @@ The need for taking a raw reference only arise because of Rust having both of th
171131
# Unresolved questions
172132
[unresolved-questions]: #unresolved-questions
173133

174-
With [SUGAR], should the lint apply to cases that are covered by the special desugaring or not?
175-
Also, if not, should the lint become `deny` eventually (maybe only on some editions)?
176-
(Without [SUGAR], the lint clearly must apply to `&mut <place> as *mut _`/`&<place> as *const _`, and that pattern is common enough that the cost of `deny` is too high.)
177-
178-
The interaction with auto-deref is a bit unfortunate.
179134
Maybe the lint should also cover cases that look like `&[mut] <place> as *[mut|const] ?T` in the surface syntax but had a method call inserted, thus manifesting a reference (with the associated guarantees).
180135
The lint as described would not fire because the reference actually gets used as such (being passed to `deref`).
181136
However, what would the lint suggest to do instead?
@@ -184,10 +139,46 @@ There just is no way to write this code without creating a reference.
184139
# Future possibilities
185140
[future-possibilities]: #future-possibilities
186141

187-
With [SUGAR], if Rust's type ascriptions end up performing coercions, those coercions should trigger the raw reference operator just like other coercions do.
142+
## "Syntactic sugar" extension
143+
144+
We could treat `&mut <place> as *mut _`/`&<place> as *const _` as if they had been written with `&raw` to avoid creating temporary references when that was likely not the intention.
145+
We could also do this when `&mut <place>`/`& <place>` is used in a coercion site and gets coerced to a raw pointer.
146+
147+
```rust
148+
let x = &X as *const T; // this is fine now
149+
let x: *const T; // this is fine if we also apply the "sugar" for coercions
150+
let x = &X as &T as *const T; // this is casting to raw but "too late" even if we adapt [SUGAR]
151+
let x = { &X } as *const T; // this is likely also too late (but should be covered by the lint)
152+
let x: *const T = if b { &X } else { &Y }; // this is likely also too late (and hopefully covered by the lint)
153+
```
154+
155+
Notice that this only applies if no automatic call to `deref` or `deref_mut` got inserted:
156+
those are regular function calls taking a reference, so in that case a reference is created and it must satisfy the usual guarantees.
157+
158+
The point of this to keep existing code working and to provide a way for projects to adjust to these rules before stabilization.
159+
Another good reason for this extension is that code could be adjusted without having to drop support for old Rust versions.
160+
161+
However, it might be surprising that the following two pieces of code are not equivalent:
162+
163+
```rust
164+
// Variant 1
165+
let x = unsafe { &packed.field }; // Undefined behavior!
166+
let x = x as *const _;
167+
// Variant 2
168+
let x = unsafe { &packed.field as *const _ }; // good code
169+
```
170+
171+
This is at least partially mitigated by the fact that the lint should fire in variant 1.
172+
173+
Another problem is that if `as` ever becomes an operation that can be overloaded, the behavior of `&packed.field as *const _` can *not* be obtained by dispatching to the overloaded `as` operator.
174+
Calling that method would assert validity of the reference.
175+
176+
In the future, if Rust's type ascriptions end up performing coercions, those coercions should trigger the raw reference operator just like other coercions do.
188177
So `&packed.field: *const _` would be `&raw const packed.field`.
189178
If Rust ever gets type ascriptions with coercions for binders, likewise these coercions would be subject to these rules in cases like `match &packed.field { x: *const _ => x }`.
190179

180+
## Other
181+
191182
It has been suggested to [remove `static mut`][static-mut] because it is too easy to accidentally create references with lifetime `'static`.
192183
With `&raw` we could instead restrict `static mut` to only allow taking raw pointers (`&raw [mut|const] STATIC`) and entirely disallow creating references (`&[mut] STATIC`) even in safe code (in a future edition, likely; with lints in older editions).
193184

0 commit comments

Comments
 (0)