Re: [PATCH 1/7] rust: file: add Rust abstraction for `struct file`
From: Benno Lossin
Date: Wed Nov 29 2023 - 18:18:08 EST
On 29.11.23 22:27, Alice Ryhl wrote:
> Another example:
>
> void set_nonblocking_and_fput(struct file *file) {
> // Let's just ignore the lock for this example.
> file->f_flags |= O_NONBLOCK;
>
> fput(file);
> }
>
> This method takes a file, sets it to non-blocking, and then destroys the
> ref-count. What are the ownership semantics? Well, the caller should own
> an `fget` ref-count, and we consume that ref-count. The equivalent Rust
> code would be to take an `ARef<File>`:
>
> fn set_nonblocking_and_fput(file: ARef<File>) {
> file.set_flag(O_NONBLOCK);
>
> // When `file` goes out of scope here, the destructor
> // runs and calls `fput`. (Since that's what we defined
> // `ARef` to do on drop in `fn dec_ref`.)
> }
>
> You can also explicitly call the destructor with `drop(file)`:
>
> fn set_nonblocking_and_fput(file: ARef<File>) {
> file.set_flag(O_NONBLOCK);
> drop(file);
>
> // When `file` goes out of scope, the destructor does
> // *not* run. This is because `drop(file)` is a move
> // (due to the signature of drop), and if you perform a
> // move, then the destructor no longer runs at the end
> // of the scope.
I want to note that while the destructor does not run at the end of the
scope, it still *does* run: the `drop(file)` call runs the destructor.
> }
>
> And note that this would not compile, because we give up ownership of
> the `ARef` by passing it to `drop`:
>
> fn set_nonblocking_and_fput(file: ARef<File>) {
> drop(file);
> file.set_flag(O_NONBLOCK);
> }
>
[...]
>>> +// SAFETY: The type invariants guarantee that `File` is always ref-counted.
>>> +unsafe impl AlwaysRefCounted for File {
>>> + fn inc_ref(&self) {
>>> + // SAFETY: The existence of a shared reference means that the refcount is nonzero.
>>> + unsafe { bindings::get_file(self.0.get()) };
>>> + }
>>
>> Why inc_ref() and not just get_file()?
>
> Whenever you see an impl block that uses the keyword "for", then the
> code is implementing a trait. In this case, the trait being implemented
> is AlwaysRefCounted, which allows File to work with ARef.
>
> It has to be `inc_ref` because that's what AlwaysRefCounted calls this
> method.
I am not sure if the Rust term "trait" is well-known, so for a bit more
context, I am quoting the Rust Book [1]:
A *trait* defines functionality a particular type has and can share
with other types. We can use traits to define shared behavior in an
abstract way. We can use *trait bounds* to specify that a generic type
can be any type that has certain behavior.
[1]: https://doc.rust-lang.org/book/ch10-02-traits.html
We have created an abstraction over reference counting:
the trait `AlwaysRefCounted` and the struct `ARef<T>` where `T`
implements `AlwaysRefCounted`.
As Alice already explained, `ARef<T>` is a pointer that owns a refcount
on the object. Because `ARef<T>` needs to know how to increment and
decrement that counter. For example, when you want to create another
`ARef<T>` you can `clone()` it and therefore `ARef<T>` needs to
increment the refcount. And when you drop it, `ARef<T>` needs to
decrement it.
The "`ARef<T>` knows how to inc/dec the refcount" part is done by the
`AlwaysRefCounted` trait. And there we chose to name the functions
`inc_ref` and `dec_ref`, since these are the *general*/*abstract*
operations and not any specific refcount adjustment.
Hope that also helped and did not create confusion.
--
Cheers,
Benno