Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add speaker notes for trait objects #193

Closed
wants to merge 13 commits into from
6 changes: 4 additions & 2 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,11 @@
- [Unsafe Rust](unsafe.md)
- [Dereferencing Raw Pointers](unsafe/raw-pointers.md)
- [Mutable Static Variables](unsafe/mutable-static-variables.md)
- [Calling Unsafe Functions](unsafe/unsafe-functions.md)
- [Extern Functions](unsafe/extern-functions.md)
- [Unions](unsafe/unions.md)
- [Calling Unsafe Functions](unsafe/calling-unsafe-functions.md)
- [Writing Unsafe Functions](unsafe/writing-unsafe-functions.md)
- [Extern Functions](unsafe/extern-functions.md)
- [Implementing Unsafe Traits](unsafe/unsafe-traits.md)
- [Exercises](exercises/day-3/afternoon.md)
- [Safe FFI Wrapper](exercises/day-3/safe-ffi-wrapper.md)

Expand Down
4 changes: 2 additions & 2 deletions src/exercises/day-3/safe-ffi-wrapper.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Safe FFI Wrapper

Rust has great support for calling functions through a _foreign function
interface_ (FFI). We will use this to build a safe wrapper the `glibc` functions
you would use from C to read the filenames of a directory.
interface_ (FFI). We will use this to build a safe wrapper for the `libc`
functions you would use from C to read the filenames of a directory.

You will want to consult the manual pages:

Expand Down
17 changes: 17 additions & 0 deletions src/traits.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,20 @@ fn main() {
}
}
```

<details>

* Traits may specify pre-implemented (default) methods and methods that users are required to implement themselves. Methods with default implementations can rely on required methods.
* Types that implement a given trait may be of different sizes. This makes it impossible to have things like `Vec<Greet>` in the example above.
* `dyn Greet` is a way to tell the compiler about a dynamically sized type that implements `Greet`.
* In the example, `pets` holds Fat Pointers to objects that implement `Greet`. The Fat Pointer consist of two components, a pointer to the actual object and a pointer to the virtual method table for the `Greet` implementation of that particular object.

Compare these outputs in the above example:
```rust,ignore
println!("{} {}", std::mem::size_of::<Dog>(), std::mem::size_of::<Cat>());
println!("{} {}", std::mem::size_of::<&Dog>(), std::mem::size_of::<&Cat>());
println!("{}", std::mem::size_of::<&dyn Greet>());
println!("{}", std::mem::size_of::<Box<dyn Greet>>());
```

</details>
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# Calling Unsafe Functions

A function or method can be marked `unsafe` if it has extra preconditions you
must uphold:
must uphold to avoid undefined behaviour:

```rust,editable
fn main() {
let emojis = "🗻∈🌏";

// Safe because the indices are in the correct order, within the bounds of
// the string slice, and lie on UTF-8 sequence boundaries.
unsafe {
// Undefined behavior if indices do not lie on UTF-8 sequence boundaries.
println!("{}", emojis.get_unchecked(0..4));
println!("{}", emojis.get_unchecked(4..7));
println!("{}", emojis.get_unchecked(7..11));
Expand Down
8 changes: 8 additions & 0 deletions src/unsafe/extern-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,11 @@ fn main() {
}
}
```

<details>

This is usually only a problem for extern functions which do things with pointers which might
violate Rust's memory model, but in general any C function might have undefined behaviour under any
arbitrary circumstances.

</details>
9 changes: 8 additions & 1 deletion src/unsafe/mutable-static-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ It is safe to read an immutable static variable:
static HELLO_WORLD: &str = "Hello, world!";

fn main() {
println!("name is: {}", HELLO_WORLD);
println!("HELLO_WORLD: {}", HELLO_WORLD);
}
```

Expand All @@ -26,3 +26,10 @@ fn main() {
unsafe { println!("COUNTER: {}", COUNTER); } // Potential data race!
}
```

<details>

Using a mutable static is generally a bad idea, but there are some cases where it might make sense
in low-level `no_std` code, such as implementing a heap allocator or working with some C APIs.

</details>
26 changes: 25 additions & 1 deletion src/unsafe/raw-pointers.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,34 @@ fn main() {
let r1 = &mut num as *mut i32;
let r2 = &num as *const i32;

// Safe because r1 and r2 were obtained from references and so are guaranteed to be non-null and
// properly aligned, the objects underlying the references from which they were obtained are
// live throughout the whole unsafe block, and they are not accessed either through the
// references or concurrently through any other pointers.
unsafe {
println!("r1 is: {}", *r1);
*r1 = 10; // Data race if r1 is being written concurrently!
*r1 = 10;
println!("r2 is: {}", *r2);
}
}
```

<details>

It is good practice (and required by the Android Rust style guide) to write a comment for each
`unsafe` block explaining how the code inside it satisfies the safety requirements of the unsafe
operations it is doing.

In the case of pointer dereferences, this means that the pointers must be
[_valid_](https://doc.rust-lang.org/std/ptr/index.html#safety), i.e.:

* The pointer must be non-null.
* The pointer must be _dereferenceable_ (within the bounds of a single allocated object).
* The object must not have been deallocated.
* There must not be concurrent accesses to the same location.
* If the pointer was obtained by casting a reference, the underlying object must be live and no
reference may be used to access the memory.

In most cases the pointer must also be properly aligned.

</details>
11 changes: 11 additions & 0 deletions src/unsafe/unions.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,14 @@ fn main() {
println!("bool: {}", unsafe { u.b }); // Undefined behavior!
}
```

<details>

Unions are very rarely needed in Rust as you can usually use an enum. They are occasionally needed
for interacting with C library APIs.

If you just want to reinterpret bytes as a different type, you probably want
[`std::mem::transmute`](https://doc.rust-lang.org/stable/std/mem/fn.transmute.html) or a safe
wrapper such as the [`zerocopy`](https://crates.io/crates/zerocopy) crate.

</details>
37 changes: 37 additions & 0 deletions src/unsafe/unsafe-traits.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Implementing Unsafe Traits

Like with functions, you can mark a trait as `unsafe` if the implementation must guarantee
particular conditions to avoid undefined behaviour.

For example, the `zerocopy` crate has an unsafe trait that looks
[something like this](https://docs.rs/zerocopy/latest/zerocopy/trait.AsBytes.html):

```rust,editable
use std::mem::size_of_val;
use std::slice;

/// ...
/// # Safety
/// The type must have a defined representation and no padding.
pub unsafe trait AsBytes {
fn as_bytes(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(self as *const Self as *const u8, size_of_val(self))
}
}
}

// Safe because u32 has a defined representation and no padding.
unsafe impl AsBytes for u32 {}
```

<details>

There should be a `# Safety` section on the Rustdoc for the trait explaining the requirements for
the trait to be safely implemented.

The actual safety section for `AsBytes` is rather longer and more complicated.

The built-in `Send` and `Sync` traits are unsafe.

</details>
38 changes: 38 additions & 0 deletions src/unsafe/writing-unsafe-functions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Writing Unsafe Functions

You can mark your own functions as `unsafe` if they require particular conditions to avoid undefined
behaviour.

```rust,editable
/// Swaps the values pointed to by the given pointers.
///
/// # Safety
///
/// The pointers must be valid and properly aligned.
unsafe fn swap(a: *mut u8, b: *mut u8) {
let temp = *a;
*a = *b;
*b = temp;
}

fn main() {
let mut a = 42;
let mut b = 66;

// Safe because ...
unsafe {
swap(&mut a, &mut b);
}

println!("a = {}, b = {}", a, b);
}
```

<details>

We wouldn't actually use pointers for this because it can be done safely with references.

Note that unsafe code is allowed within an unsafe function without an `unsafe` block. We can
prohibit this with `#[deny(unsafe_op_in_unsafe_fn)]`. Try adding it and see what happens.

</details>