Skip to content

Commit c07ac40

Browse files
mgeislerdjmitcheqwandor
authored
Split large unsafe function slide (#2406)
The old slice was doing several things at the same time: demonstrating both external functions as well as unsafe Rust functions. We now treat those two topics separately. In addition, the “Calling Unsafe Functions” heading has become its own slide with a non-crashing example that shows what can go wrong if an argument is misunderstood in a call to an unsafe function. The old example didn’t actually illustrate the danger clearly: it would produce mangled UTF-8 output, which the Playground server refuses to print. Part of #2445. --------- Co-authored-by: Dustin J. Mitchell <[email protected]> Co-authored-by: Andrew Walbran <[email protected]>
1 parent 153bd63 commit c07ac40

File tree

5 files changed

+144
-87
lines changed

5 files changed

+144
-87
lines changed

src/SUMMARY.md

+3
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,9 @@
222222
- [Mutable Static Variables](unsafe-rust/mutable-static.md)
223223
- [Unions](unsafe-rust/unions.md)
224224
- [Unsafe Functions](unsafe-rust/unsafe-functions.md)
225+
- [Unsafe Rust Functions](unsafe-rust/unsafe-functions/rust.md)
226+
- [Unsafe External Functions](unsafe-rust/unsafe-functions/extern-c.md)
227+
- [Calling Unsafe Functions](unsafe-rust/unsafe-functions/calling.md)
225228
- [Unsafe Traits](unsafe-rust/unsafe-traits.md)
226229
- [Exercise: FFI Wrapper](unsafe-rust/exercise.md)
227230
- [Solution](unsafe-rust/solution.md)

src/unsafe-rust/unsafe-functions.md

+6-87
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,19 @@
11
---
2-
minutes: 5
2+
minutes: 15
33
---
44

55
# Unsafe Functions
66

7-
## Calling Unsafe Functions
8-
97
A function or method can be marked `unsafe` if it has extra preconditions you
10-
must uphold to avoid undefined behaviour:
11-
12-
```rust,editable
13-
extern "C" {
14-
fn abs(input: i32) -> i32;
15-
}
16-
17-
fn main() {
18-
let emojis = "🗻∈🌏";
19-
20-
// SAFETY: The indices are in the correct order, within the bounds of the
21-
// string slice, and lie on UTF-8 sequence boundaries.
22-
unsafe {
23-
println!("emoji: {}", emojis.get_unchecked(0..4));
24-
println!("emoji: {}", emojis.get_unchecked(4..7));
25-
println!("emoji: {}", emojis.get_unchecked(7..11));
26-
}
27-
28-
println!("char count: {}", count_chars(unsafe { emojis.get_unchecked(0..7) }));
29-
30-
// SAFETY: `abs` doesn't deal with pointers and doesn't have any safety
31-
// requirements.
32-
unsafe {
33-
println!("Absolute value of -3 according to C: {}", abs(-3));
34-
}
35-
36-
// Not upholding the UTF-8 encoding requirement breaks memory safety!
37-
// println!("emoji: {}", unsafe { emojis.get_unchecked(0..3) });
38-
// println!("char count: {}", count_chars(unsafe {
39-
// emojis.get_unchecked(0..3) }));
40-
}
41-
42-
fn count_chars(s: &str) -> usize {
43-
s.chars().count()
44-
}
45-
```
46-
47-
## Writing Unsafe Functions
8+
must uphold to avoid undefined behaviour.
489

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

52-
```rust,editable
53-
/// Swaps the values pointed to by the given pointers.
54-
///
55-
/// # Safety
56-
///
57-
/// The pointers must be valid and properly aligned.
58-
unsafe fn swap(a: *mut u8, b: *mut u8) {
59-
let temp = *a;
60-
*a = *b;
61-
*b = temp;
62-
}
63-
64-
fn main() {
65-
let mut a = 42;
66-
let mut b = 66;
67-
68-
// SAFETY: ...
69-
unsafe {
70-
swap(&mut a, &mut b);
71-
}
72-
73-
println!("a = {}, b = {}", a, b);
74-
}
75-
```
12+
- Rust functions declared unsafe with `unsafe fn`.
13+
- Foreign functions in `extern "C"` blocks.
7614

7715
<details>
7816

79-
## Calling Unsafe Functions
80-
81-
`get_unchecked`, like most `_unchecked` functions, is unsafe, because it can
82-
create UB if the range is incorrect. `abs` is unsafe for a different reason: it
83-
is an external function (FFI). Calling external functions is usually only a
84-
problem when those functions do things with pointers which might violate Rust's
85-
memory model, but in general any C function might have undefined behaviour under
86-
any arbitrary circumstances.
87-
88-
The `"C"` in this example is the ABI;
89-
[other ABIs are available too](https://doc.rust-lang.org/reference/items/external-blocks.html).
90-
91-
## Writing Unsafe Functions
92-
93-
We wouldn't actually use pointers for a `swap` function - it can be done safely
94-
with references.
95-
96-
Note that unsafe code is allowed within an unsafe function without an `unsafe`
97-
block. We can prohibit this with `#[deny(unsafe_op_in_unsafe_fn)]`. Try adding
98-
it and see what happens. This will likely change in a future Rust edition.
17+
We will look at the two kinds of unsafe functions next.
9918

10019
</details>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Calling Unsafe Functions
2+
3+
Failing to uphold the safety requirements breaks memory safety!
4+
5+
```rust,editable
6+
#[derive(Debug)]
7+
#[repr(C)]
8+
struct KeyPair {
9+
pk: [u16; 4], // 8 bytes
10+
sk: [u16; 4], // 8 bytes
11+
}
12+
13+
const PK_BYTE_LEN: usize = 8;
14+
15+
fn log_public_key(pk_ptr: *const u16) {
16+
let pk: &[u16] = unsafe { std::slice::from_raw_parts(pk_ptr, PK_BYTE_LEN) };
17+
println!("{pk:?}");
18+
}
19+
20+
fn main() {
21+
let key_pair = KeyPair { pk: [1, 2, 3, 4], sk: [0, 0, 42, 0] };
22+
log_public_key(key_pair.pk.as_ptr());
23+
}
24+
```
25+
26+
Always include a safety comment for each `unsafe` block. It must explain why the
27+
code is actually safe. This example is missing a safety comment and is unsound.
28+
29+
<details>
30+
31+
Key points:
32+
33+
- The second argument to `slice::from_raw_parts` is the number of _elements_,
34+
not bytes! This example demonstrates unexpected behavior by reading past the
35+
end of one array and into another.
36+
- This is not actually undefined behaviour, as `KeyPair` has a defined
37+
representation (due to `repr(C)`) and no padding, so the contents of the
38+
second array is also valid to read through the same pointer.
39+
- `log_public_key` should be unsafe, because `pk_ptr` must meet certain
40+
prerequisites to avoid undefined behaviour. A safe function which can cause
41+
undefined behaviour is said to be `unsound`. What should its safety
42+
documentation say?
43+
- The standard library contains many low-level unsafe functions. Prefer the safe
44+
alternatives when possible!
45+
- If you use an unsafe function as an optimization, make sure to add a benchmark
46+
to demonstrate the gain.
47+
48+
</details>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Unsafe External Functions
2+
3+
Functions in a foreign language may also be unsafe:
4+
5+
```rust,editable
6+
use std::ffi::c_char;
7+
8+
unsafe extern "C" {
9+
// `abs` doesn't deal with pointers and doesn't have any safety requirements.
10+
safe fn abs(input: i32) -> i32;
11+
12+
/// # Safety
13+
///
14+
/// `s` must be a pointer to a NUL-terminated C string which is valid and
15+
/// not modified for the duration of this function call.
16+
unsafe fn strlen(s: *const c_char) -> usize;
17+
}
18+
19+
fn main() {
20+
println!("Absolute value of -3 according to C: {}", abs(-3));
21+
22+
unsafe {
23+
// SAFETY: We pass a pointer to a C string literal which is valid for
24+
// the duration of the program.
25+
println!("String length: {}", strlen(c"String".as_ptr()));
26+
}
27+
}
28+
```
29+
30+
<details>
31+
32+
- Rust used to consider all extern functions unsafe, but this changed in Rust
33+
1.82 with `unsafe extern` blocks.
34+
- `abs` must be explicitly marked as `safe` because it is an external function
35+
(FFI). Calling external functions is usually only a problem when those
36+
functions do things with pointers which which might violate Rust's memory
37+
model, but in general any C function might have undefined behaviour under any
38+
arbitrary circumstances.
39+
- The `"C"` in this example is the ABI;
40+
[other ABIs are available too](https://doc.rust-lang.org/reference/items/external-blocks.html).
41+
- Note that there is no verification that the Rust function signature matches
42+
that of the function definition -- that's up to you!
43+
44+
</details>
+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Unsafe Rust Functions
2+
3+
You can mark your own functions as `unsafe` if they require particular
4+
preconditions to avoid undefined behaviour.
5+
6+
```rust,editable
7+
/// Swaps the values pointed to by the given pointers.
8+
///
9+
/// # Safety
10+
///
11+
/// The pointers must be valid, properly aligned, and not otherwise accessed for
12+
/// the duration of the function call.
13+
unsafe fn swap(a: *mut u8, b: *mut u8) {
14+
let temp = *a;
15+
*a = *b;
16+
*b = temp;
17+
}
18+
19+
fn main() {
20+
let mut a = 42;
21+
let mut b = 66;
22+
23+
// SAFETY: The pointers must be valid, aligned and unique because they came
24+
// from references.
25+
unsafe {
26+
swap(&mut a, &mut b);
27+
}
28+
29+
println!("a = {}, b = {}", a, b);
30+
}
31+
```
32+
33+
<details>
34+
35+
We wouldn't actually use pointers for a `swap` function --- it can be done
36+
safely with references.
37+
38+
Note that unsafe code is allowed within an unsafe function without an `unsafe`
39+
block. We can prohibit this with `#[deny(unsafe_op_in_unsafe_fn)]`. Try adding
40+
it and see what happens. This will
41+
[change in the 2024 Rust edition](https://github.com/rust-lang/rust/issues/120535).
42+
43+
</details>

0 commit comments

Comments
 (0)