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

Split large unsafe function slide #2406

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,9 @@
- [Mutable Static Variables](unsafe-rust/mutable-static.md)
- [Unions](unsafe-rust/unions.md)
- [Unsafe Functions](unsafe-rust/unsafe-functions.md)
- [Unsafe External Functions](unsafe-rust/unsafe-extern-c-functions.md)
- [Unsafe Rust Functions](unsafe-rust/unsafe-rust-functions.md)
- [Calling Unsafe Functions](unsafe-rust/calling-unsafe-functions.md)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For subslides, we've used a subdirectory to contain the subslides. Also, only the top-level slide has the timing information. See src/methods-and-traits/traits for an example.

- [Unsafe Traits](unsafe-rust/unsafe-traits.md)
- [Exercise: FFI Wrapper](unsafe-rust/exercise.md)
- [Solution](unsafe-rust/solution.md)
Expand Down
47 changes: 47 additions & 0 deletions src/unsafe-rust/calling-unsafe-functions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
minutes: 5
---

# Calling Unsafe Functions

Failing to uphold the safety requirements breaks memory safety!

```rust,editable
#[derive(Debug)]
#[repr(C)]
struct KeyPair {
pk: [u16; 4], // 8 bytes
sk: [u16; 4], // 8 bytes
}

const PK_BYTE_LEN: usize = 8;

fn log_public_key(pk_ptr: *const u16) {
let pk: &[u16] = unsafe { std::slice::from_raw_parts(pk_ptr, PK_BYTE_LEN) };
println!("{pk:?}");
}

fn main() {
let key_pair = KeyPair { pk: [1, 2, 3, 4], sk: [0, 0, 42, 0] };
log_public_key(key_pair.pk.as_ptr());
}
```

Always include a safety comment for each `unsafe` block. It must explain why the
code is actually safe. This example is missing a safety comment and has UB.

<details>

Key points:

- The second argument to `slice::from_raw_parts` is the number of _elements_,
not bytes! This example demonstrate undefined behavior by reading past the end
of one array and into another.

- The standard library contains many low-level unsafe functions. Prefer the safe
alternatives when possible!

- If you use an unsafe function as an optimization, make sure to add a benchmark
to demonstrate the gain.

</details>
33 changes: 33 additions & 0 deletions src/unsafe-rust/unsafe-extern-c-functions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
minutes: 3
---

# Unsafe External Functions

All functions implemented in a foreign language are considered unsafe in Rust:

```rust,editable
extern "C" {
fn abs(input: i32) -> i32;
}

fn main() {
// SAFETY: `abs` doesn't deal with pointers and doesn't have any safety
// requirements.
unsafe {
println!("Absolute value of -3 according to C: {}", abs(-3));
}
}
```

<details>

`abs` is unsafe because it is an external function (FFI). Calling external
functions is usually only a problem when those functions 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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth mentioning here that there is no automatic checking of the Rust function signature against that of the target function? For example, in this case the C function might take uint64_t and this would cause UB.


The `"C"` in this example is the ABI;
[other ABIs are available too](https://doc.rust-lang.org/reference/items/external-blocks.html).

</details>
93 changes: 6 additions & 87 deletions src/unsafe-rust/unsafe-functions.md
Original file line number Diff line number Diff line change
@@ -1,100 +1,19 @@
---
minutes: 5
minutes: 1
---

# Unsafe Functions

## Calling Unsafe Functions

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

```rust,editable
extern "C" {
fn abs(input: i32) -> i32;
}

fn main() {
let emojis = "🗻∈🌏";

// SAFETY: The indices are in the correct order, within the bounds of the
// string slice, and lie on UTF-8 sequence boundaries.
unsafe {
println!("emoji: {}", emojis.get_unchecked(0..4));
println!("emoji: {}", emojis.get_unchecked(4..7));
println!("emoji: {}", emojis.get_unchecked(7..11));
}

println!("char count: {}", count_chars(unsafe { emojis.get_unchecked(0..7) }));

// SAFETY: `abs` doesn't deal with pointers and doesn't have any safety
// requirements.
unsafe {
println!("Absolute value of -3 according to C: {}", abs(-3));
}

// Not upholding the UTF-8 encoding requirement breaks memory safety!
// println!("emoji: {}", unsafe { emojis.get_unchecked(0..3) });
// println!("char count: {}", count_chars(unsafe {
// emojis.get_unchecked(0..3) }));
}

fn count_chars(s: &str) -> usize {
s.chars().count()
}
```

## Writing Unsafe Functions
must uphold to avoid undefined behaviour.

You can mark your own functions as `unsafe` if they require particular
conditions to avoid undefined behaviour.
There are two main categories:

```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;

// SAFETY: ...
unsafe {
swap(&mut a, &mut b);
}

println!("a = {}, b = {}", a, b);
}
```
- Foreign functions in `extern "C"` blocks.
- Rust functions declared unsafe with `unsafe fn`.

<details>

## Calling Unsafe Functions

`get_unchecked`, like most `_unchecked` functions, is unsafe, because it can
create UB if the range is incorrect. `abs` is incorrect for a different reason:
it is an external function (FFI). Calling external functions is usually only a
problem when those functions 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.

The `"C"` in this example is the ABI;
[other ABIs are available too](https://doc.rust-lang.org/reference/items/external-blocks.html).

## Writing Unsafe Functions

We wouldn't actually use pointers for a `swap` function - 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. This will likely change in a future Rust edition.
We will look at the two kinds of unsafe functions next.

</details>
44 changes: 44 additions & 0 deletions src/unsafe-rust/unsafe-rust-functions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
minutes: 3
---

# Unsafe Rust 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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

..and must not overlap!

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;

// SAFETY: ...
unsafe {
swap(&mut a, &mut b);
}

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

<details>

We wouldn't actually use pointers for a `swap` function --- 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. This will likely change in a future Rust edition.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rust-lang/rust#120535 in the 2024 edition, in fact!


</details>