Three implementations of the same functionality, I fail to see why they all work

I am implementing binary tree functionality for a Node struct, more precisely the size of the tree rooted by any given node. The relevant code looks like this:

use std::rc::{Rc, Weak};
use std::cell::RefCell;

type Link<T> = Rc<RefCell<Node<T>>>;
type WeakLink<T> = Weak<RefCell<Node<T>>>;


pub struct Node<T> {
    value: T,
    parent: Option<WeakLink<T>>,
    left: Option<Link<T>>,
    right: Option<Link<T>>,
}

impl<T> Node<T> {
    pub fn size(link: &Link<T>) -> usize {
        let mut size = 0;
        let mut links = vec![Rc::clone(link)];
        let mut link;

        while links.len() > 0 {
            size += 1;
            link = links.remove(0);

            if let Some(left_link) = &link.borrow().left {
                links.push(Rc::clone(left_link));
            }
            if let Some(right_link) = &link.borrow().right {
                links.push(Rc::clone(right_link));
            }
        }
        size
    }
}

This actually works as intended, but I wondered if it would be possible to do it without the link variable. Inspired by the help from a user in this forum, I went for this:

pub fn size(link: &Link<T>) -> usize {
    let mut size = 0;
    let mut links = vec![Rc::clone(link)];
    // LINK NOT DECLARED HERE ANYMORE

    while links.len() > 0 {
        size += 1;
        let link = links.remove(0); // LINK NOW DECLARED HERE

        if let Some(left_link) = &link.borrow().left {
            links.push(Rc::clone(left_link));
        }
        if let Some(right_link) = &link.borrow().right {
            links.push(Rc::clone(right_link));
        }
        drop(link); // IF THIS IS TO WORK, IT SHOULD BE DROPPED HERE. BUT WHY?
    }
    size
}

This doesn't work unless I drop link right before the end of the block. Isn't link going to be dropped anyway immediately after the second if let anyway?

But there's even more wizardy waiting:

pub fn size(link: &Link<T>) -> usize {
    let mut size = 0;
    let mut links = vec![Rc::clone(link)];

    while links.len() > 0 {
        size += 1;
        let link = links.remove(0);

        if let Some(left_link) = &link.borrow().left {
            links.push(Rc::clone(left_link));
        }
        if let Some(right_link) = &link.borrow().right {
            links.push(Rc::clone(right_link));
        }; // JUST ADD THIS SEMICOLON AND IT WORKS TOO???
    }
    size
}

Remove the drop, add ; at the end of the second if let and it works too!
At this point I am completely lost about what Rust is doing, I suspect that the second and third definitions are somehow doing the exact same thing but I fail to see why. Any light casted over this will be very welcomed.

You only need the drop or semicolon before edition 2024. You need it on earlier editions because if let ... bindings lasted as if this was a desugared match -- they lasted through the entire expression:

The semicolon makes the last if let a statement instead of the tail expression of the while loop block. That was another edition change:

Here it is working on edition 2024 with no drop or semicolon.

4 Likes