Can't borrow var as immutable after scoped mutable borrow

I'm trying to mutate a struct using a function filling some optional fields. After I filled the fields I want to use the struct to do whatever, but I always get the following error.

error[E0502]: cannot borrow `txn` as immutable because it is also borrowed as mutable
   --> src/asynch/transaction/mod.rs:263:26
    |
259 |             let txn1 = &mut txn;
    |                        -------- mutable borrow occurs here
...
263 |         println!("{:?}", txn);
    |                          ^^^
    |                          |
    |                          immutable borrow occurs here
    |                          mutable borrow later used here
    |
    = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

Here is the actual test code:

#[tokio::test]
    async fn test_autofill_txn() -> Result<()> {
        let wallet = Wallet::create(None).unwrap();
        let mut txn = OfferCreate::new(
            wallet.classic_address.clone().into(),
            None,
            None,
            None,
            Some(72779837),
            None,
            Some(1),
            None,
            None,
            None,
            XRPAmount::from("1000000").into(),
            IssuedCurrencyAmount::new(
                "USD".into(),
                "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq".into(),
                "0.3".into(),
            )
            .into(),
            None,
            None,
        );
        let client = AsyncWebsocketClient::<SingleExecutorMutex, _>::open(
            "wss://testnet.xrpl-labs.com/".parse().unwrap(),
        )
        .await
        .unwrap();
        {
            let txn1 = &mut txn;
            autofill(txn1, &client, None).await.unwrap();
        }
        println!("{:?}", txn);

        Ok(())
    }

I figured that when I have autofill in a separate scope, the mutable reference txn1 gets dropped after the autofilling is complete and I can then borrow txn immutable again to print it.
I thought so because of the following code snipped from the book:

    let mut s = String::from("hello");

    {
        let r1 = &mut s;
    } // r1 goes out of scope here, so we can make a new reference with no problems.

    let r2 = &mut s;

You can also view the complete code on github.

I'm pretty sure you have the classic &'a mut T<'a> antipattern here, because autofill takes &'a T: Transaction<'a, ...> as argument. Mutable references are invariant in T, so a &'a mut T<'a> reference lives as long as T does, hence your problem with the two simultaneous borrows. You should remodel your bounds to avoid this, i.e. 'b: 'a, T: Transaction<'b, ...>.

4 Likes

Thank you for your quick answer. I am not sure whether I have understood these concepts correctly. Here is how I tried to implement it:

pub async fn autofill<'a, 'b, F, T>(
    transaction: &'a mut T,
    client: &'a impl AsyncClient<'b>,
    signers_count: Option<u8>,
) -> Result<()>
where
    'b: 'a,
    T: Transaction<'b, F> + Model + Clone,
    F: IntoEnumIterator + Serialize + Debug + PartialEq + 'b,
{ ... }

The compiler then told me that 'a also needs to outlive 'b. So I also added 'a: 'b, resulting in the following code which throws the same error as before:

pub async fn autofill<'a, 'b, F, T>(
    transaction: &'a mut T,
    client: &'a impl AsyncClient<'b>,
    signers_count: Option<u8>,
) -> Result<()>
where
    'b: 'a,
    'a: 'b,
    T: Transaction<'b, F> + Model + Clone,
    F: IntoEnumIterator + Serialize + Debug + PartialEq + 'b,
{ ... }
error[E0502]: cannot borrow `txn` as immutable because it is also borrowed as mutable
   --> src/asynch/transaction/mod.rs:263:26
    |
260 |             let txn1 = &mut txn;
    |                        -------- mutable borrow occurs here
...
263 |         println!("{:?}", txn);
    |                          ^^^
    |                          |
    |                          immutable borrow occurs here
    |                          mutable borrow later used here
    |
    = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

F can't live for 'b, because you can't get a 'long from a &'short mut T<'long>.

You cannot get a &'long U or any &mut U from a &'short &'long mut U

2 Likes

I noticed that autofill is calling other methods that also have this Rust antipattern:

async fn calculate_fee_per_transaction_type<'a, T, F>(
    transaction: T,
    client: Option<&'a impl AsyncClient<'a>>,  // <<<<<<<<<<<<<<<<

In addition you're using the same lifetime for many references which could be unrelated in scope. Can you explain why both these params have the same &'a lifetime, or did you just give them the same lifetime arbitrarily?

pub async fn autofill<'a, F, T>(
    transaction: &'a mut T,
    client: &'a impl AsyncClient<'a>,

One approach is to remove all the lifetime annotations and then add them back in only if the compiler requires them and you understand why. You could ask here if you don't understand why a lifetime annotation is needed, and how it should be assigned.

1 Like

Turns out I had a misunderstanding of how lifetimes work. I see a lot of improvement now where i can use shorter lifetimes or just let the borrow checker infer them. I will refactor my code and may come back to you with an update. Thanks for providing me with the resources to find out about this misunderstanding @jofas and @jumpnbrownweasel

1 Like

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.