Skip to content

Commit 1aabd25

Browse files
committed
cleanup more comments
1 parent e6d2d18 commit 1aabd25

File tree

1 file changed

+20
-20
lines changed

1 file changed

+20
-20
lines changed

text/0000-unsafe-pointer-reform.md

+20-20
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ However these static functions are fairly cumbersome in the common case, where y
5353

5454
## Signed Offset
5555

56-
The cast in `ptr.offset(idx as isize)` is unnecessarily annoying. Idiomatic Rust code uses unsigned offsets, but the low level code has to constantly cast those offsets. To understand why this interface is designed as it is, some background is neeeded.
56+
The cast in `ptr.offset(idx as isize)` is unnecessarily annoying. Idiomatic Rust code uses unsigned offsets, but low level code is forced to constantly cast those offsets. To understand why this interface is designed as it is, some background is neeeded.
5757

5858
`offset` is directly exposing LLVM's `getelementptr` instruction, with the `inbounds` keyword. `wrapping_offset` removes the `inbounds` keyword. `offset` takes a signed integer, because that's what GEP exposes. It's understandable that we've been conservative here; GEP is so confusing that it has an [entire FAQ](http://llvm.org/docs/GetElementPtr.html).
5959

@@ -69,20 +69,20 @@ That said, LLVM is pretty candid that it models pointers as two's complement int
6969
>
7070
> As such, there are some ramifications of this for inbounds GEPs: scales implied by array/vector/pointer indices are always known to be “nsw” since they are signed values that are scaled by the element size. These values are also allowed to be negative (e.g. “`gep i32 *%P, i32 -1`”) but the pointer itself is logically treated as an unsigned value. This means that GEPs have an asymmetric relation between the pointer base (which is treated as unsigned) and the offset applied to it (which is treated as signed). The result of the additions within the offset calculation cannot have signed overflow, but when applied to the base pointer, there can be signed overflow.
7171
72-
This is written in a bit of a confusing way, so here's a simplified summary of what we care about:
72+
This is written in a bit of a confusing way, so here's a simplified summary of what we care about:
7373

74-
* The pointer is treated as an unsigned number, and the offset as signed.
75-
* While computing the offset in bytes (`idx * size_of::<T>()`), we aren't allowed to do signed overflow (nsw).
74+
* The pointer is treated as an unsigned number, and the offset as signed.
75+
* While computing the offset in bytes (`idx * size_of::<T>()`), we aren't allowed to do signed overflow (nsw).
7676
* While applying the offset to the pointer (`ptr + offset`), we aren't allowed to do unsigned overflow (nuw).
7777

78-
Part of the historical argument for signed offset in Rust has been a *warning* against these overflow concerns, but upon inspection that doesn't really make sense.
78+
Part of the historical argument for signed offset in Rust has been a *warning* against these overflow concerns, but upon inspection that doesn't really make sense.
7979

80-
* If you offset a `*const i16` by `isize::MAX / 3 * 2` (which fits into a signed integer), then you'll still overflow a signed integer in the implicit `offset` computation.
80+
* If you offset a `*const i16` by `isize::MAX / 3 * 2` (which fits into a signed integer), then you'll still overflow a signed integer in the implicit `offset` computation.
8181
* There's no indication that unsigned overflow should be a concern at all.
8282
* The location of the offset *isn't even* the place to handle this issue. The ultimate consequence of `offset` being signed is that LLVM can't support allocations larger than `isize::MAX` bytes. Therefore this issue should be handled at the level of memory allocation code.
8383
* The fact that `offset` is `unsafe` is already surprising to anyone with the "it's just addition" mental model, pushing them to read the documentation and learn the actual rules.
8484

85-
In conclusion: `as isize` doesn't actually help our developers write better code.
85+
In conclusion: `as isize` doesn't help developers write better code.
8686

8787

8888

@@ -95,16 +95,18 @@ In conclusion: `as isize` doesn't actually help our developers write better code
9595

9696
Add the following method equivalents for the static `ptr` functions on `*const T` and `*mut T`:
9797

98+
(Note that this proposal doesn't deprecate the static functions, as they still make some code more ergonomic than methods, and we'd like to avoid regressing the ergonomics of any usecase. More discussion can be found in the alternatives.)
99+
98100
```rust
99101
impl<T> *(const|mut) T {
100102
unsafe fn read(self) -> T;
101103
unsafe fn read_volatile(self) -> T;
102104
unsafe fn read_unaligned(self) -> T;
103105

104-
// NOTE: name changed from the original static fn to make it
106+
// NOTE: name changed from the original static fn to make it
105107
// easier to remember argument order
106108
unsafe fn copy_to(self, dest: *mut T, count: usize);
107-
unsafe fn copy_to_nonoverlapping(self, src: *mut T, count: usize);
109+
unsafe fn copy_to_nonoverlapping(self, dest: *mut T, count: usize);
108110
}
109111
```
110112

@@ -113,7 +115,7 @@ And these only on `*mut T`:
113115
```rust
114116
impl<T> *mut T {
115117
// note that I've moved these from both to just `*mut T`, to go along with `copy_from`
116-
unsafe fn drop_in_place(self);
118+
unsafe fn drop_in_place(self) where T: ?Sized;
117119
unsafe fn write(self, val: T);
118120
unsafe fn write_bytes(self, val: u8, count: usize);
119121
unsafe fn write_volatile(self, val: T);
@@ -123,14 +125,12 @@ impl<T> *mut T {
123125
}
124126
```
125127

126-
Note that this proposal doesn't deprecate the static functions, as they still make some code more ergonomic than methods, and we'd like to avoid regressing the ergonomics of any usecase. (More discussion can be found in the alternatives.)
127-
128128

129129

130130

131131
## Unsigned Offset
132132

133-
Add the following conveniences to both `*const T` and `*mut T`:
133+
Add the following conveniences to both `*const T` and `*mut T`:
134134

135135
```rust
136136
impl<T> *(const|mut) T {
@@ -141,7 +141,7 @@ impl<T> *(const|mut) T {
141141
}
142142
```
143143

144-
I expect `ptr.add` to replace ~95% of all uses of `ptr.offset`, and `ptr.sub` to replace ~95% of the remaining 5%. It's just very rare to have an offset that you don't know the sign of, and don't need special handling for.
144+
I expect `ptr.add` to replace ~95% of all uses of `ptr.offset`, and `ptr.sub` to replace ~95% of the remaining 5%. It's very rare to have an offset that you don't know the sign of, and *also* don't need special handling for.
145145

146146

147147

@@ -174,23 +174,23 @@ The only drawback I can think of is that this introduces a "what is idiomatic" s
174174

175175
## Overload operators for more ergonomic offsets
176176

177-
Rust doesn't support "unsafe operators", and `offset` is an unsafe function because of the semantics of GetElementPointer. We could make `wrapping_add` be the implementation of `+`, but almost no code should actually be using wrapping offsets, so we shouldn't do anything to make it seem "preferred" over non-wrapping offsets.
177+
Rust doesn't support "unsafe operators", and `offset` is an unsafe function because of the semantics of GetElementPointer. We could make `wrapping_add` be the implementation of `+`, but almost no code should actually be using wrapping offsets, so we shouldn't do anything to make it seem "preferred" over non-wrapping offsets.
178178

179179
Beyond that, `(ptr + idx).read_volatile()` is a bit wonky to write -- methods chain better than operators.
180180

181181

182182

183183

184-
## Make `offset` generic
184+
## Make `offset` generic
185185

186-
You could make `offset` generic so it accepts `usize` and `isize`. However you would still want the `sub` method, and at that point you might as well have `add` for symmetry. Also `add` is shorter which is a nice carrot for users to migrate to it.
186+
We could make `offset` generic so it accepts `usize` and `isize`. However we would still want the `sub` method, and at that point we might as well have `add` for symmetry. Also `add` is shorter which is a nice carrot for users to migrate to it.
187187

188188

189189

190190

191191
## `copy_from`
192192

193-
`copy` is the only mutating `ptr` operation that doesn't write to the *first* argument. In fact, it's clearly backwards compared to C's memcpy. Instead it's ordered in analogy to `fs::copy`.
193+
`copy` is the only mutating `ptr` operation that doesn't write to the *first* argument. In fact, it's clearly backwards compared to C's memcpy. Instead it's ordered in analogy to `fs::copy`.
194194

195195
Methodization could be an opportunity to "fix" this, and reorder the arguments. I don't have any strong feelings for which order is better, but I am concerned that doing this reordering will lead to developer errors. Either in translating to the new methods, or in using the method order when deciding to use the old functions.
196196

@@ -208,9 +208,9 @@ To avoid any issues with the methods and static functions coexisting, we could d
208208
<*mut _>::foo(ptr);
209209
```
210210

211-
I personally consider this a minor ergonomic and readability regression from `ptr::foo(ptr)`, and so would rather not do this.
211+
I personally consider this a minor ergonomic and readability regression from `ptr::foo(ptr)`, and so would rather not do this.
212212

213-
More importantly, this would cause needless churn for old code which is still perfectly *fine*, if a bit less ergonomic than it could be. More ergonomic interfaces should be adopted based on their own merits; not because This Is The New Way, And Everyone Should Do It The New Way.
213+
More importantly, this would cause needless churn for old code which is still perfectly *fine*, if a bit less ergonomic than it could be. More ergonomic interfaces should be adopted based on their own merits; not because This Is The New Way, And Everyone Should Do It The New Way.
214214

215215
In fact, even if we decide we should deprecate these functions, we should still stagger the deprecation out several releases to minimize ecosystem churn. When a deprecation occurs, users of the latest compiler will be pressured by diagnostics to update their code to the new APIs. If those APIs were introduced in the same release, then they'll be making their library only compile on the latest release, effectively breaking the library for anyone who hasn't had a chance to upgrade yet. If the deprecation were instead done several releases later, then by the time users are pressured to use the new APIs there will be a buffer of several stable releases that can compile code using the new APIs.
216216

0 commit comments

Comments
 (0)