# Size of unsafe blocks

**URL:** <https://users.rust-lang.org/t/size-of-unsafe-blocks/136352>\
**Category:** community\
**Created:** [November 18, 2025, 5:29am UTC](https://users.rust-lang.org/t/size-of-unsafe-blocks/136352 "2025-11-18T05:29:57Z")\
**Posts on this page:** 16\
**Page:** 1

<div class="post-metadata">

**Author:** ![robofinch](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/robofinch/32/49287_2.png) [@robofinch](https://users.rust-lang.org/u/robofinch)\
**Post date:** [November 18, 2025, 5:29am UTC](https://users.rust-lang.org/t/size-of-unsafe-blocks/136352/1 "2025-11-18T05:29:57Z")

</div>

> [@TWiR quote of the week](https://users.rust-lang.org/t/twir-quote-of-the-week/328/1731):
>
> Except that where you place the boundaries of an unsafe block only has meaning to humans, so long as they're large enough to encompass the actual `unsafe` operations.
> 
> The lethally strong robot arm's reach may potentially extend very far outside the `unsafe` block because public/private access modifiers only affect things _outside_ the containing `mod`, so anything inside the `mod` has the power to break invariants the `unsafe` code may depend on.

Continuing [this discussion](https://users.rust-lang.org/t/twir-quote-of-the-week/328/1731) from quote of the week; how does everyone prefer to handle the scope of `unsafe` blocks?

Personally, I enable the `unsafe_code` lint (at the `warn` level), and add `#![expect(unsafe_code, reason = "..")]` at the top of modules with any unsafe (and I keep unsafe blocks small). I’m also a fan of relatively verbose safety comments, and using small `unsafe` blocks is an easy way to require that each `unsafe` action gets its own explanation of why its safety invariants are upheld. I suppose it’s still possible for someone to modify other safe code in the module without bothering to read all the comments, but if that happened, my solution would probably be to add more comments to the safe code as well.

---

<div class="post-metadata">

**Author:** ![zirconium-n](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/zirconium-n/32/23899_2.png) [@zirconium-n](https://users.rust-lang.org/u/zirconium-n)\
**Post date:** [November 18, 2025, 5:45am UTC](https://users.rust-lang.org/t/size-of-unsafe-blocks/136352/2 "2025-11-18T05:45:19Z")

</div>

My principle: Unsafe block is the minimum collective regions of code that, modifying any code that is not in unsafe block cannot results UB or unsound code.

Reasoning: You should be able to mess with "the safe part" of the code however you want. This is how I interpret "fearless".

---

<div class="post-metadata">

**Author:** ![SkiFire13](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/skifire13/32/17959_2.png) [@SkiFire13](https://users.rust-lang.org/u/SkiFire13)\
**Post date:** [November 18, 2025, 6:53am UTC](https://users.rust-lang.org/t/size-of-unsafe-blocks/136352/3 "2025-11-18T06:53:12Z")

</div>

> [@zirconium-n](#):
>
> modifying any code that is not in unsafe block

I assume you meant code that's outside the region and not in an unsafe block?

---

<div class="post-metadata">

**Author:** ![rikyborg](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/rikyborg/32/40672_2.png) [@rikyborg](https://users.rust-lang.org/u/rikyborg)\
**Post date:** [November 18, 2025, 7:55am UTC](https://users.rust-lang.org/t/size-of-unsafe-blocks/136352/4 "2025-11-18T07:55:31Z")

</div>

When thinking about the size of unsafe blocks, I find the first two examples in [Working with Unsafe - The Rustonomicon](https://doc.rust-lang.org/nomicon/working-with-unsafe.html) quite useful.

---

<div class="post-metadata">

**Author:** ![convallaria](https://avatars.discourse-cdn.com/v4/letter/c/96bed5/32.png) [@convallaria](https://users.rust-lang.org/u/convallaria)\
**Post date:** [November 18, 2025, 4:33pm UTC](https://users.rust-lang.org/t/size-of-unsafe-blocks/136352/5 "2025-11-18T16:33:51Z")

</div>

My personal `unsafe` scoping policy is that I try to minimize:

1. the number of safe operations within unsafe blocks
2. the number of unsafe operations in any given function
3. the size of unsafe traits
4. the number of safety-critical invariants that callers of unsafe functions must uphold
5. the number of safety-critical invariants that implementers of unsafe traits must uphold
6. the size of unsafe modules
7. the number of (especially non-`std`) `use` declarations within unsafe modules
8. the scope of (especially non-`std`) `use` declarations within unsafe modules

where an unsafe module is a module that either contains unsafe code or is imported by an unsafe module. As a result of rule 1, my unsafe blocks are usually pretty small.

Admittedly, this can be a little extreme, but it _really_ helps with debugging. Each of my policies reduces the number of places I have to check when/if I find UB:

- Rules 1-2 make it easy to figure out which unsafe function/operation is causing UB (assuming I have good tests).
- Once I've figured out which unsafe function/operation is causing the UB, rules 3-5 make it easy to figure out which invariant is being broken.
- Once I've figured out which invariant is being broken, rules 6-8 minimize the amount of code I have to read to figure out _how_ the invariant is being broken.

Of my rules, rule 1 is, in my opinion, the least important. Figuring out which unsafe operation was the final cause of the UB is usually the easy part of debugging, at least for me. That said, rule 1 does provide some advantages, and I find most arguments for expanding unsafe blocks to be pretty weak (since, as mentioned in the ['Nomicon page](https://doc.rust-lang.org/nomicon/working-with-unsafe.html) that @rikyborg linked, unsafe code pollutes the whole module).

---

<div class="post-metadata">

**Author:** ![tczajka](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/tczajka/32/24743_2.png) [@tczajka](https://users.rust-lang.org/u/tczajka)\
**Post date:** [November 18, 2025, 6:00pm UTC](https://users.rust-lang.org/t/size-of-unsafe-blocks/136352/6 "2025-11-18T18:00:11Z")

</div>

I only mark the actual, individual `unsafe` calls.

> [@zirconium-n](#):
>
> My principle: Unsafe block is the minimum collective regions of code that, modifying any code that is not in unsafe block cannot results UB or unsound code.

That's unworkable. Say you have this code:

```rust
fn half(n: usize) -> usize {
    n / 2
}

pub fn weird_sum(a: &[u32]) -> u32 {
    let mut s = 0;
    for i in 0..a.len() {
        let j = half(i);
        // SAFETY: j <= i < a.len()
        s += unsafe { a.get_unchecked(j) };
    }
    s
}

```

According to your policy, you'd have to write:

```rust
fn half(n: usize) -> usize {
    unsafe { n / 2 }
}

pub fn weird_sum(a: &[u32]) -> u32 {
    let mut s = 0;
    unsafe {
        for i in 0..a.len() {
            let j = half(i);
            s += a.get_unchecked(j);
        }
    }
    s
}

```

---

<div class="post-metadata">

**Author:** ![zirconium-n](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/zirconium-n/32/23899_2.png) [@zirconium-n](https://users.rust-lang.org/u/zirconium-n)\
**Post date:** [November 19, 2025, 2:14am UTC](https://users.rust-lang.org/t/size-of-unsafe-blocks/136352/7 "2025-11-19T02:14:14Z")

</div>

Valid criticism. I'm sure I don't want to do this. But I'm sure I don't want to shrink unsafe block to the mininum that let the compiler happy either.

I need some time to think through this.

---

<div class="post-metadata">

**Author:** ![mroth](https://avatars.discourse-cdn.com/v4/letter/m/a8b319/32.png) [@mroth](https://users.rust-lang.org/u/mroth)\
**Post date:** [November 19, 2025, 12:51pm UTC](https://users.rust-lang.org/t/size-of-unsafe-blocks/136352/8 "2025-11-19T12:51:42Z")

</div>

Of course, this is just an example. But as an example, it also shows that by inlining (when practicable) the unsafe block can appear “fearless” from the outside:

```rust
unsafe {
    for i in 0..a.len() {
        s += a.get_unchecked(i / 2);
    }
}

```

I’d tend to say that a lot of unsafe usage in practice can be treated this way, which makes the rule, or maybe better, the advice stated by @zirconium-n perfectly usable and contributes to robustness.

Maybe it’s like “good engineering practice” in safe code: write things so they are comprehensible and robust when modified. A comparable rule for unsafe code could be to formulate unsafe blocks so that they are self-contained and insulated from changes around them.

---

<div class="post-metadata">

**Author:** ![tczajka](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/tczajka/32/24743_2.png) [@tczajka](https://users.rust-lang.org/u/tczajka)\
**Post date:** [November 19, 2025, 1:34pm UTC](https://users.rust-lang.org/t/size-of-unsafe-blocks/136352/9 "2025-11-19T13:34:53Z")

</div>

> [@mroth](#):
>
> A comparable rule for unsafe code could be to formulate unsafe blocks so that they are self-contained and insulated from changes around them.

I think `unsafe` is the wrong abstraction for this kind of encapsulation. **Modules** are the appropriate encapsulation tool, not `unsafe` markers.

Say you have a module that defines a type that maintains certain invariants. Moreover, it uses `unsafe` that relies on the invariants for correctness. This philosophy would imply that **all** the code in the module must be marked `unsafe` because it all has access that allows it to mess up the invariants.

A better philosophy is: make a **module** to encapsulate the minimal functionality required to maintain invariants.

`unsafe` markers are for when you use your invariants to call dangerous functions in **other** modules, rather than to mark all the code that has access to your invariants.

---

<div class="post-metadata">

**Author:** ![convallaria](https://avatars.discourse-cdn.com/v4/letter/c/96bed5/32.png) [@convallaria](https://users.rust-lang.org/u/convallaria)\
**Post date:** [November 19, 2025, 2:06pm UTC](https://users.rust-lang.org/t/size-of-unsafe-blocks/136352/10 "2025-11-19T14:06:16Z")

</div>

I think that some of the differences in unsafe block size preferences might come down to what we're using unsafe for. Most (if not all) unsafe exists on a sliding scale. On one end is code like in your example, where the user is effectively telling the compiler "Hey, in this one instance, you don't need to do a runtime check." On the other end is stuff like very low level data structures and algorithms, where the user is effectively telling the compiler "Trust me, my abstraction around this pile of raw pointer shenanigans is totally sound."

For use cases on the former end of the scale, it may be workable to have "fearless" unsafe blocks like this. However, when I write unsafe code, I'm almost always writing code that's on the latter end of the scale. (Moreover, I suspect that unsafe-to-avoid-runtime-checks will become rarer and rarer as the type system improves in expressiveness.) The more complex and state-dependent the unsafe becomes, the more this "fearless" unsafe block principle becomes unworkable.

To illustrate, take your example, but pretend that `a` is not an instance of `std::vec::Vec`, but of some type `crate::MyCollection`. Suddenly, `crate::MyCollection::len` is safety critical, and the unsafe block is no longer fearless.

That said, I don't think that these large unsafe code blocks are necessarily bad practice for everyone. I avoid them, but as I said earlier, that's mostly personal preference, and I think the disadvantages of them are relatively minor.

---

<div class="post-metadata">

**Author:** ![jonh](https://avatars.discourse-cdn.com/v4/letter/j/dec6dc/32.png) [@jonh](https://users.rust-lang.org/u/jonh)\
**Post date:** [November 19, 2025, 3:21pm UTC](https://users.rust-lang.org/t/size-of-unsafe-blocks/136352/11 "2025-11-19T15:21:22Z")

</div>

Today I learned of the [expect attribute](https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-expect-attribute).

Not sure I like the warning being suppressed. The `expect` would typically not be seen if looking(/editing) at top of a crate. (`allow` if used also suppresses.)

```rust
#![warn(unsafe_code)]
mod bar {
    #![expect(unsafe_code, reason = "it is wrong not having some unsafety")]
    pub fn foo() {
        unsafe { std::arch::asm!(""); }
    }
}
fn main() { bar::foo(); }

```

---

<div class="post-metadata">

**Author:** ![khimru](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/khimru/32/31344_2.png) [@khimru](https://users.rust-lang.org/u/khimru)\
**Post date:** [November 19, 2025, 3:41pm UTC](https://users.rust-lang.org/t/size-of-unsafe-blocks/136352/12 "2025-11-19T15:41:24Z")

</div>

That's much more workable approach. Just like with real robot: you normally don't place the operator booth into a caged area where lethal robot is operating, yet you still mark it specially and control who can enter it.

---

<div class="post-metadata">

**Author:** ![robofinch](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/robofinch/32/49287_2.png) [@robofinch](https://users.rust-lang.org/u/robofinch)\
**Post date:** [November 19, 2025, 4:11pm UTC](https://users.rust-lang.org/t/size-of-unsafe-blocks/136352/13 "2025-11-19T16:11:49Z")

</div>

One example from a crate I’ve written, at the top of a module:

```rust
#![cfg_attr(
    not(feature = "polonius"),
    expect(unsafe_code, reason = "needed to satisfy NLL borrowck"),
)]

```

I have the `unsafe_code` lint globally enabled in my Cargo.toml. Using explicit carve-outs seems like a nice way to lint that I'm using `unsafe` exactly where and when I intend to. And in this next case, the `reason` more-or-less explains that there's a specific `ManuallyDrop` necessitating `unsafe` somewhere in the module:

```rust
#![expect(unsafe_code, reason = "manually drop database lockfile inside Drop impl")]

```

I guess this relates more to linting preferences rather than `unsafe` preferences, since I do something similar for `indexing_slicing`; I don't want to use `index_unchecked` (even if I _could_), and... I guess I'm too lazy to use a more functional style. But I want to add more friction to panics to make sure I've thought through my actions, and thus I enable clippy's `indexing_slicing` lint with `expect` on each slice index callsite (serving a purpose similar to a SAFETY comment).

---

<div class="post-metadata">

**Author:** ![jonh](https://avatars.discourse-cdn.com/v4/letter/j/dec6dc/32.png) [@jonh](https://users.rust-lang.org/u/jonh)\
**Post date:** [November 19, 2025, 8:13pm UTC](https://users.rust-lang.org/t/size-of-unsafe-blocks/136352/14 "2025-11-19T20:13:19Z")

</div>

That's what `allow` is for. `expect` on the other hand is for addition of creating a warning (or error if paired with deny/forbid) if the code is changed and the lint is no longer in effect.

You have used `#![warn(clippy::indexing_slicing)]` and `#[expect(clippy::indexing_slicing)]` and nothing is currently reported since compiler is dumb and thinks there could be a panic. In a future compiler, it figures out there is no such panic, now printed is a warning.

edit: (two stones at once. Don't want to add another excessive posting.)  
Swapped deny to warn which I intended (was a copy past from playground I missed thinking about.)

p.s. Just add, I do think it's a good technique in use of the `indexing_slicing` lint. (`allow`/`expect` difference is more of a triviality.)

---

<div class="post-metadata">

**Author:** ![robofinch](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/robofinch/32/49287_2.png) [@robofinch](https://users.rust-lang.org/u/robofinch)\
**Post date:** [November 19, 2025, 9:15pm UTC](https://users.rust-lang.org/t/size-of-unsafe-blocks/136352/15 "2025-11-19T21:15:29Z")

</div>

Huh, I'd assumed that `indexing_slicing` applied when indexing or slicing is used _at all_, but you're right, the description is "Checks for usage of indexing or slicing that may panic at runtime."

Either way, I normally don't `deny` Clippy lints (just `warn`), so I guess I'm fine if a future compiler version can prove it doesn't panic and warns about the `expect`; it couldn't be a breaking change. But I would probably then need to change it to `allow`, assuming that it'd take time to percolate back from nightly to MSRV, since writing a full `cfg_attr` with `rustversion` seems too tedious.

---

<div class="post-metadata">

**Author:** ![system](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/system/32/51372_2.png) [@system](https://users.rust-lang.org/u/system)\
**Post date:** [February 17, 2026, 9:16pm UTC](https://users.rust-lang.org/t/size-of-unsafe-blocks/136352/16 "2026-02-17T21:16:21Z")

</div>

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.
