[PATCH v2 1/4] rust: aref: document safety of ARef doctest example
Cian McGuire <[email protected]> Sat, 25 Jul 2026 00:38:22 +0100
| Newsgroups | org.kernel.vger.rust-for-linux,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
The `into_raw` doctest contains two `unsafe` items whose SAFETY comments were left as "TODO.": the `AlwaysRefCounted` impl for the example's `Empty` type, and the subsequent `ARef::from_raw` call. Both are sound for the same reason: `Empty`'s `dec_ref` is a no-op and never deallocates anything. `AlwaysRefCounted`'s "keep alive until matching decrement" guarantee therefore holds vacuously here -- no decrement in this impl ever frees the object, so it cannot be freed while a live increment remains. The object's actual lifetime is governed entirely by ordinary Rust scoping instead: `data` is a stack value that outlives every use of `ptr`/`data_ref`/`raw_ptr`, none of which is ever dereferenced in the example. The same fact justifies the `from_raw` call. Elsewhere in this file, `Clone` and `From<&T>` justify their own calls to `from_raw` by pointing at a preceding `inc_ref()` call, but no such call happens in this example. That is fine here specifically because `Empty` does not track a real reference count at all, so there is no accounting for `from_raw` to violate, regardless of whether an increment "really" occurred. Suggested-by: Miguel Ojeda <[email protected]> Link: https://github.com/Rust-for-Linux/linux/issues/351 Signed-off-by: Cian McGuire <[email protected]> --- v2: - Reworked the `AlwaysRefCounted for Empty` comment: v1 only argued the `dec_ref` half of the trait's contract; now it also explains why the no-op `dec_ref` means nothing is ever freed through this impl, so the "keep alive until matching decrement" guarantee cannot be violated, and notes neither pointer is dereferenced in the example. - Reworded the `from_raw` comment to state concretely which invariant is vacuous, instead of just asserting "vacuously satisfied". rust/kernel/sync/aref.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/rust/kernel/sync/aref.rs b/rust/kernel/sync/aref.rs index b721b2e00b98..37dcd0a0f406 100644 --- a/rust/kernel/sync/aref.rs +++ b/rust/kernel/sync/aref.rs @@ -124,7 +124,11 @@ pub unsafe fn from_raw(ptr: NonNull<T>) -> Self { /// /// struct Empty {} /// - /// # // SAFETY: TODO. + /// # // SAFETY: `dec_ref` is a no-op and never deallocates `data`, so the object's + /// # // lifetime is governed entirely by ordinary Rust scoping, not by this impl; the + /// # // trait's "keep alive until matching decrement" guarantee holds vacuously because + /// # // no decrement here ever frees anything, and neither `ptr` nor `raw_ptr` is ever + /// # // dereferenced in this example. /// unsafe impl AlwaysRefCounted for Empty { /// fn inc_ref(&self) {} /// unsafe fn dec_ref(_obj: NonNull<Self>) {} @@ -132,7 +136,9 @@ pub unsafe fn from_raw(ptr: NonNull<T>) -> Self { /// /// let mut data = Empty {}; /// let ptr = NonNull::<Empty>::new(&mut data).unwrap(); - /// # // SAFETY: TODO. + /// # // SAFETY: `from_raw`'s contract concerns a reference count that `Empty` doesn't + /// # // actually have; since `inc_ref`/`dec_ref` never touch any state, there is no + /// # // invariant here for `from_raw` to violate. /// let data_ref: ARef<Empty> = unsafe { ARef::from_raw(ptr) }; /// let raw_ptr: NonNull<Empty> = ARef::into_raw(data_ref); /// -- 2.55.0