it's just a limitation of the rule for temporary expressions. the immutable reference is created as a temporary expression. although it is only used as argument to the set_len() method, the borrow lives as long as the entire statement. quote from the reference:
The drop scope of the temporary is usually the end of the enclosing statement.
Note that exclusive ownership of a file descriptor does not imply exclusive ownership of the underlying kernel object that the file descriptor references (also called “open file description” on some operating systems). File descriptors basically work like Arc: when you receive an owned file descriptor, you cannot know whether there are any other file descriptors that reference the same kernel object.
...including via Rust APIs like OwnedFd::try_clone and BorrowedFd::try_clone_to_owned. So a File is notionally a struct NewType(Arc<Mutex<FileDescription>>) or so, where you can change the internal state of a shared object via &self-taking methods.
The file descriptor underling a File itself is not mutated; the file or other abstract file object that is behind OS-provided and OS-synchronized abstraction is mutated. The file descriptor is just some index or other key into some OS-maintained process state data structure.
This is from memory, so caveat reader: this is a case where the tree-borrow model may eventually relax this limitation.
At the moment there's a special case for foo.mut_method(foo.ref_method()); is treated as:
- ref foo for mut_method
- ref foo for ref_method
- upgrade ref foo for mut_method to a mut ref
This works if ref_method doesn't return a borrow from foo there's no ref and mut ref simultaneously.
But the reverse doesn't work because the inner ref is mut and cant exist when the outer ref exists.
My understanding is if tree borrowing is considered to be sound, then the borrow checker would be ok to arbitrarily reorder borrows that are not dependent and could allow both of these examples without an exception (I do not believe this actually changes if you can have a mut ref and a ref simultaneously, but I'm not too familiar). There's an argument perhaps that the target should have been evaluated last, but it's a breaking change now and might just cause other issues.
This is because you're thinking of file. as a no-op, but the . in between is actually an operation that creates loans of objects, and may call Deref methods.
The order of operations is easier to see when you imagine every reference being taken through an explicit function call:
In this code it's more visible that get_file() runs first, to get the object that you can call methods on, and then get_file_exclusive() runs, while you still have to keep the result of get_file() to use it next.
let pos = get_file_exclusive().stream_position()?;
get_file().set_len(pos)?;
In this case get_file_exclusive() runs first instead, and the file reference it returned doesn't have to be kept after it returns the pos.
References in Rust are like read-write locks, but evaluated at compile time. &mut tries to get an exclusive lock and can't if there's any & also locking it for reading at the same time.