uniffi-rs icon indicating copy to clipboard operation
uniffi-rs copied to clipboard

Question on optimizing our use of `Arc`s

Open thunderbiscuit opened this issue 1 year ago • 1 comments
trafficstars

We have this type (simplified for this issue):

pub struct TxOut {
    pub value: Arc<Amount>,
    pub number: u32,
}

Which we need to move to and from its Rust equivalent (which we type alias as RustTxOut):

impl From<TxOut> for RustTxOut {
    fn from(tx_out: TxOut) -> Self {
        let value = match Arc::try_unwrap(tx_out.value) {
            Ok(val) => val.0,
            Err(arc) => arc.0.clone(),
        };

        RustTxOut {
            value,
            script_pubkey,
        }
    }
}

A dev proposed this Arc::try_unwrap() optimization to remove the requirement for the clone if the Arc counter is at 1. This looks good, but once I attempted to clear my understanding of the exact behaviour here through some testing, I'm left wondering if the Arc can ever be 1 and this optimization ever hit the Ok case. See my issue here for full details.

The gist of it is that after testing Kotlin using the following:

  1. First I added this method on one of my types that would trigger this "consuption of the Arc":
pub fn use_tx_out(&self, tx_out: TxOut) -> String {
    println!("Reference count before try_unwrap: {}", Arc::strong_count(&tx_out.value));
    let rust_tx_out: BitcoinTxOut = tx_out.into();
    "Your TxOut has been consumed".to_string()
}

And then used it in a test:

val output1: LocalOutput = wallet.listOutput().first()
val message = wallet.useTxOut(output1.txout)
println("Message: $message")
println("TxOut is: ${output1.txout.value.toSat()}")

The printed output is:

Reference count before try_unwrap: 2

ArcTest > testArcs() STANDARD_OUT
    Balance: 2024456
    Message: Your TxOut has been consumed
    TxOut is: 4200

So the test succeeds in printing the TxOut after consuming it in a method and we know why in this case: the reference count is 2. My question is now... why is the count at 2 here? ChatGPT seems to think it might be that uniffi holds an extra reference somewhere for you, but you know what they say about ChatGPT; NEVER TRUST THE MOFO WITH PRODUCTION CODE.

My question is then:

  1. can someone help me clarify this for myself? Does uniffi hold an inner reference to all my complex types anyway and this type of optimization will never actually trigger because both my Kotlin code and uniffi will have a reference?
  2. What do you think of these type of optimizations? Do they play into uniffi's turf too much and should be avoided? Or is that something that makes total sense and is a good idea to adopt widely in our codebase?

thunderbiscuit avatar Sep 19 '24 14:09 thunderbiscuit