1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-03-07 01:10:09 +02:00

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 <djmitche@google.com>
Co-authored-by: Andrew Walbran <qwandor@google.com>
This commit is contained in:
Martin Geisler 2025-02-05 15:00:14 +01:00 committed by GitHub
parent 153bd63cc0
commit c07ac40f90
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 144 additions and 87 deletions

View File

@ -222,6 +222,9 @@
- [Mutable Static Variables](unsafe-rust/mutable-static.md)
- [Unions](unsafe-rust/unions.md)
- [Unsafe Functions](unsafe-rust/unsafe-functions.md)
- [Unsafe Rust Functions](unsafe-rust/unsafe-functions/rust.md)
- [Unsafe External Functions](unsafe-rust/unsafe-functions/extern-c.md)
- [Calling Unsafe Functions](unsafe-rust/unsafe-functions/calling.md)
- [Unsafe Traits](unsafe-rust/unsafe-traits.md)
- [Exercise: FFI Wrapper](unsafe-rust/exercise.md)
- [Solution](unsafe-rust/solution.md)

View File

@ -1,100 +1,19 @@
---
minutes: 5
minutes: 15
---
# 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:
must uphold to avoid undefined behaviour.
```rust,editable
extern "C" {
fn abs(input: i32) -> i32;
}
There are two main categories:
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
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;
// SAFETY: ...
unsafe {
swap(&mut a, &mut b);
}
println!("a = {}, b = {}", a, b);
}
```
- Rust functions declared unsafe with `unsafe fn`.
- Foreign functions in `extern "C"` blocks.
<details>
## Calling Unsafe Functions
`get_unchecked`, like most `_unchecked` functions, is unsafe, because it can
create UB if the range is incorrect. `abs` is unsafe 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>

View File

@ -0,0 +1,48 @@
# 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 is unsound.
<details>
Key points:
- The second argument to `slice::from_raw_parts` is the number of _elements_,
not bytes! This example demonstrates unexpected behavior by reading past the
end of one array and into another.
- This is not actually undefined behaviour, as `KeyPair` has a defined
representation (due to `repr(C)`) and no padding, so the contents of the
second array is also valid to read through the same pointer.
- `log_public_key` should be unsafe, because `pk_ptr` must meet certain
prerequisites to avoid undefined behaviour. A safe function which can cause
undefined behaviour is said to be `unsound`. What should its safety
documentation say?
- 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>

View File

@ -0,0 +1,44 @@
# Unsafe External Functions
Functions in a foreign language may also be unsafe:
```rust,editable
use std::ffi::c_char;
unsafe extern "C" {
// `abs` doesn't deal with pointers and doesn't have any safety requirements.
safe fn abs(input: i32) -> i32;
/// # Safety
///
/// `s` must be a pointer to a NUL-terminated C string which is valid and
/// not modified for the duration of this function call.
unsafe fn strlen(s: *const c_char) -> usize;
}
fn main() {
println!("Absolute value of -3 according to C: {}", abs(-3));
unsafe {
// SAFETY: We pass a pointer to a C string literal which is valid for
// the duration of the program.
println!("String length: {}", strlen(c"String".as_ptr()));
}
}
```
<details>
- Rust used to consider all extern functions unsafe, but this changed in Rust
1.82 with `unsafe extern` blocks.
- `abs` must be explicitly marked as `safe` because it is an external function
(FFI). Calling external functions is usually only a problem when those
functions do things with pointers which 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).
- Note that there is no verification that the Rust function signature matches
that of the function definition -- that's up to you!
</details>

View File

@ -0,0 +1,43 @@
# Unsafe Rust Functions
You can mark your own functions as `unsafe` if they require particular
preconditions to avoid undefined behaviour.
```rust,editable
/// Swaps the values pointed to by the given pointers.
///
/// # Safety
///
/// The pointers must be valid, properly aligned, and not otherwise accessed for
/// the duration of the function call.
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: The pointers must be valid, aligned and unique because they came
// from references.
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
[change in the 2024 Rust edition](https://github.com/rust-lang/rust/issues/120535).
</details>