Skip to content

Commit a2c06c5

Browse files
committed
---
yaml --- r: 236026 b: refs/heads/stable c: 069681a h: refs/heads/master v: v3
1 parent b3c4630 commit a2c06c5

File tree

4 files changed

+175
-27
lines changed

4 files changed

+175
-27
lines changed

[refs]

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ refs/heads/tmp: afae2ff723393b3ab4ccffef6ac7c6d1809e2da0
2929
refs/tags/1.0.0-alpha.2: 4c705f6bc559886632d3871b04f58aab093bfa2f
3030
refs/tags/homu-tmp: f859507de8c410b648d934d8f5ec1c52daac971d
3131
refs/tags/1.0.0-beta: 8cbb92b53468ee2b0c2d3eeb8567005953d40828
32-
refs/heads/stable: 8f531d9d7e058222cb13a9987a80431906d524b9
32+
refs/heads/stable: 069681a95327d8b49a26a8fd60aa6a9ecaaf52c4
3333
refs/tags/1.0.0: 55bd4f8ff2b323f317ae89e254ce87162d52a375
3434
refs/tags/1.1.0: bc3c16f09287e5545c1d3f76b7abd54f2eca868b
3535
refs/tags/1.2.0: f557861f822c34f07270347b94b5280de20a597e

branches/stable/conversions.md

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,8 @@ applied.
168168

169169

170170

171+
TODO: receiver coercions?
172+
171173

172174
# Casts
173175

@@ -212,11 +214,10 @@ For numeric casts, there are quite a few cases to consider:
212214
* casting from a larger integer to a smaller integer (e.g. u8 -> u32) will
213215
* zero-extend if the target is unsigned
214216
* sign-extend if the target is signed
215-
* casting from a float to an integer will:
216-
* round the float towards zero if finite
217+
* casting from a float to an integer will round the float towards zero
217218
* **NOTE: currently this will cause Undefined Behaviour if the rounded
218219
value cannot be represented by the target integer type**. This is a bug
219-
and will be fixed.
220+
and will be fixed. (TODO: figure out what Inf and NaN do)
220221
* casting from an integer to float will produce the floating point representation
221222
of the integer, rounded if necessary (rounding strategy unspecified).
222223
* casting from an f32 to an f64 is perfect and lossless.
@@ -226,21 +227,41 @@ For numeric casts, there are quite a few cases to consider:
226227
is finite but larger or smaller than the largest or smallest finite
227228
value representable by f32**. This is a bug and will be fixed.
228229

229-
The casts involving rawptrs also allow us to completely bypass type-safety
230-
by re-interpretting a pointer of T to a pointer of U for arbitrary types, as
231-
well as interpret integers as addresses. However it is impossible to actually
232-
*capitalize* on this violation in Safe Rust, because derefencing a raw ptr is
233-
`unsafe`.
234-
235-
236230

237231

238232
# Conversion Traits
239233

240-
TODO
234+
TODO?
241235

242236

243237

244238

245239
# Transmuting Types
246240

241+
Get out of our way type system! We're going to reinterpret these bits or die
242+
trying! Even though this book is all about doing things that are unsafe, I really
243+
can't emphasize that you should deeply think about finding Another Way than the
244+
operations covered in this section. This is really, truly, the most horribly
245+
unsafe thing you can do in Rust. The railguards here are dental floss.
246+
247+
`mem::transmute<T, U>` takes a value of type `T` and reinterprets it to have
248+
type `U`. The only restriction is that the `T` and `U` are verified to have the
249+
same size. The ways to cause Undefined Behaviour with this are mind boggling.
250+
251+
* First and foremost, creating an instance of *any* type with an invalid state
252+
is going to cause arbitrary chaos that can't really be predicted.
253+
* Transmute has an overloaded return type. If you do not specify the return type
254+
it may produce a surprising type to satisfy inference.
255+
* Making a primitive with an invalid value is UB
256+
* Transmuting between non-repr(C) types is UB
257+
* Transmuting an & to &mut is UB
258+
* Transmuting to a reference without an explicitly provided lifetime
259+
produces an [unbound lifetime](lifetimes.html#unbounded-lifetimes)
260+
261+
`mem::transmute_copy<T, U>` somehow manages to be *even more* wildly unsafe than
262+
this. It copies `size_of<U>` bytes out of an `&T` and interprets them as a `U`.
263+
The size check that `mem::transmute` has is gone (as it may be valid to copy
264+
out a prefix), though it is Undefined Behaviour for `U` to be larger than `T`.
265+
266+
Also of course you can get most of the functionality of these functions using
267+
pointer casts.

branches/stable/intro.md

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ stack or heap, we will not explain the syntax.
2323
* [Uninitialized Memory](uninitialized.html)
2424
* [Ownership-oriented resource management (RAII)](raii.html)
2525
* [Concurrency](concurrency.html)
26+
* [Example: Implementing Vec](vec.html)
2627

2728

2829

@@ -232,10 +233,6 @@ struct Vec<T> {
232233
// We currently live in a nice imaginary world of only postive fixed-size
233234
// types.
234235
impl<T> Vec<T> {
235-
fn new() -> Self {
236-
Vec { ptr: heap::EMPTY, len: 0, cap: 0 }
237-
}
238-
239236
fn push(&mut self, elem: T) {
240237
if self.len == self.cap {
241238
// not important for this example
@@ -246,17 +243,6 @@ impl<T> Vec<T> {
246243
self.len += 1;
247244
}
248245
}
249-
250-
fn pop(&mut self) -> Option<T> {
251-
if self.len > 0 {
252-
self.len -= 1;
253-
unsafe {
254-
Some(ptr::read(self.ptr.offset(self.len as isize)))
255-
}
256-
} else {
257-
None
258-
}
259-
}
260246
}
261247
```
262248

branches/stable/vec.md

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
% Example: Implementing Vec
2+
3+
To bring everything together, we're going to write `std::Vec` from scratch.
4+
Because the all the best tools for writing unsafe code are unstable, this
5+
project will only work on nightly (as of Rust 1.2.0).
6+
7+
First off, we need to come up with the struct layout. Naively we want this
8+
design:
9+
10+
```
11+
struct Vec<T> {
12+
ptr: *mut T,
13+
cap: usize,
14+
len: usize,
15+
}
16+
```
17+
18+
And indeed this would compile. Unfortunately, it would be incorrect. The compiler
19+
will give us too strict variance, so e.g. an `&Vec<&'static str>` couldn't be used
20+
where an `&Vec<&'a str>` was expected. More importantly, it will give incorrect
21+
ownership information to dropck, as it will conservatively assume we don't own
22+
any values of type `T`. See [the chapter on ownership and lifetimes]
23+
(lifetimes.html) for details.
24+
25+
As we saw in the lifetimes chapter, we should use `Unique<T>` in place of `*mut T`
26+
when we have a raw pointer to an allocation we own:
27+
28+
29+
```
30+
#![feature(unique)]
31+
32+
use std::ptr::Unique;
33+
34+
pub struct Vec<T> {
35+
ptr: Unique<T>,
36+
cap: usize,
37+
len: usize,
38+
}
39+
```
40+
41+
As a recap, Unique is a wrapper around a raw pointer that declares that:
42+
43+
* We own at least one value of type `T`
44+
* We are Send/Sync iff `T` is Send/Sync
45+
* Our pointer is never null (and therefore `Option<Vec>` is null-pointer-optimized)
46+
47+
That last point is subtle. First, it makes `Unique::new` unsafe to call, because
48+
putting `null` inside of it is Undefined Behaviour. It also throws a
49+
wrench in an important feature of Vec (and indeed all of the std collections):
50+
an empty Vec doesn't actually allocate at all. So if we can't allocate,
51+
but also can't put a null pointer in `ptr`, what do we do in
52+
`Vec::new`? Well, we just put some other garbage in there!
53+
54+
This is perfectly fine because we already have `cap == 0` as our sentinel for no
55+
allocation. We don't even need to handle it specially in almost any code because
56+
we usually need to check if `cap > len` or `len > 0` anyway. The traditional
57+
Rust value to put here is `0x01`. The standard library actually exposes this
58+
as `std::rt::heap::EMPTY`. There are quite a few places where we'll want to use
59+
`heap::EMPTY` because there's no real allocation to talk about but `null` would
60+
make the compiler angry.
61+
62+
All of the `heap` API is totally unstable under the `alloc` feature, though.
63+
We could trivially define `heap::EMPTY` ourselves, but we'll want the rest of
64+
the `heap` API anyway, so let's just get that dependency over with.
65+
66+
So:
67+
68+
```rust
69+
#![feature(alloc)]
70+
71+
use std::rt::heap::EMPTY;
72+
use std::mem;
73+
74+
impl<T> Vec<T> {
75+
fn new() -> Self {
76+
assert!(mem::size_of::<T>() != 0, "We're not ready to handle ZSTs");
77+
unsafe {
78+
// need to cast EMPTY to the actual ptr type we want, let
79+
// inference handle it.
80+
Vec { ptr: Unique::new(heap::EMPTY as *mut _), len: 0, cap: 0 }
81+
}
82+
}
83+
}
84+
```
85+
86+
I slipped in that assert there because zero-sized types will require some
87+
special handling throughout our code, and I want to defer the issue for now.
88+
Without this assert, some of our early drafts will do some Very Bad Things.
89+
90+
Next we need to figure out what to actually do when we *do* want space. For that,
91+
we'll need to use the rest of the heap APIs. These basically allow us to
92+
talk directly to Rust's instance of jemalloc.
93+
94+
We'll also need a way to handle out-of-memory conditions. The standard library
95+
calls the `abort` intrinsic, but calling intrinsics from normal Rust code is a
96+
pretty bad idea. Unfortunately, the `abort` exposed by the standard library
97+
allocates. Not something we want to do during `oom`! Instead, we'll call
98+
`std::process::exit`.
99+
100+
```rust
101+
fn oom() {
102+
::std::process::exit(-9999);
103+
}
104+
```
105+
106+
Okay, now we can write growing:
107+
108+
```rust
109+
fn grow(&mut self) {
110+
unsafe {
111+
let align = mem::min_align_of::<T>();
112+
let elem_size = mem::size_of::<T>();
113+
114+
let (new_cap, ptr) = if self.cap == 0 {
115+
let ptr = heap::allocate(elem_size, align);
116+
(1, ptr)
117+
} else {
118+
let new_cap = 2 * self.cap;
119+
let ptr = heap::reallocate(*self.ptr as *mut _,
120+
self.cap * elem_size,
121+
new_cap * elem_size,
122+
align);
123+
(new_cap, ptr)
124+
};
125+
126+
// If allocate or reallocate fail, we'll get `null` back
127+
if ptr.is_null() { oom() }
128+
129+
self.ptr = Unique::new(ptr as *mut _);
130+
self.cap = new_cap;
131+
}
132+
}
133+
```
134+
135+
There's nothing particularly tricky in here: if we're totally empty, we need
136+
to do a fresh allocation. Otherwise, we need to reallocate the current pointer.
137+
Although we have a subtle bug here with the multiply overflow.
138+
139+
TODO: rest of this
140+
141+

0 commit comments

Comments
 (0)