I ran across an interesting error from the borrower checker that I encountered while refactoring a trait to make it object safe. One of the changes I made was to convert a generic parameter for the trait Iterator<Item = &[u8]>
:
fn verify<'a, I: Iterator<Item = &'a [u8]>>(&self, parts: I, signature: &[u8]) -> Result<()>;
to an object parameter of type &mut dyn Iterator<Item = &[u8]>
:
fn verify(&self, parts: &mut dyn Iterator<Item = &[u8]>, signature: &[u8]) -> Result<()>;
I decided to use a mutable borrow so as to allow the caller to pass a reference to a stack allocated object. This necessitated adding &mut
in front of the parts
argument at every call site for verify
, but that wasn't too bad. The interesting thing occurred when I updated this test:
fn sign_verify_test(creds: &TpmCreds) -> Result<()> {
let data: [u8; 1024] = rand_array()?;
let parts = [data.as_slice()];
let sig = creds.sign(parts.into_iter())?;
creds.verify(parts.into_iter(), sig.as_slice())
}
to this:
fn sign_verify_test(creds: &TpmCreds) -> Result<()> {
let data: [u8; 1024] = rand_array()?;
let parts = [data.as_slice()];
let sig = creds.sign(parts.into_iter())?;
creds.verify(&mut parts.into_iter(), sig.as_slice())
}
(the only difference is that &mut
is present in the first argument to verify
in the second version). This resulted in a borrow checker error:
error[E0597]: `data` does not live long enough
--> crates/btlib/src/crypto/tpm.rs:1656:22
|
1655 | let data: [u8; 1024] = rand_array()?;
| ---- binding `data` declared here
1656 | let parts = [data.as_slice()];
| ^^^^^^^^^^^^^^^ borrowed value does not live long enough
...
1659 | creds.verify(&mut parts.into_iter(), sig.as_slice())
| ----------------- a temporary with access to the borrow is created here ...
1660 | }
| -
| |
| `data` dropped here while still borrowed
| ... and the borrow might be used here, when that temporary is dropped and runs the `Drop` code for type `std::array::IntoIter`
|
= note: the temporary is part of an expression at the end of a block;
consider forcing this temporary to be dropped sooner, before the block's local variables are dropped
help: for example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block
|
1659 | let x = creds.verify(&mut parts.into_iter(), sig.as_slice()); x
| +++++++
Following the compiler's suggestion did indeed resolve the error, but I wanted to really understand what's happening here.
My understanding is that the temporary struct created by parts.into_iter()
is being dropped at the end of the block, and since this struct contains a reference to data
, which is being dropped at the same point, there is an error because borrowed data does not live strictly longer than the borrower. My guess as to why the compiler's suggestion solves the problem is that in the modified code, the temporary is dropped at the end of the expression, which is before the point where data
is dropped, so there is no issue. Is this reasoning correct?
If this is what's going on, I'm a bit surprised. I would've expected the compiler to drop the temporary at the end of the expression in which it occurs, even if this expression occurs at the end of a block. Is there a reason why the compiler doesn't do this?