Search code examples
rustrust-macros

How does `tokio::pin` change the type of variable?


use std::pin::Pin;

fn print_type_of<T>(_: &T) {
    println!("{}", std::any::type_name::<T>())
}

fn test(s: String) {}
fn test2(s: Pin<&mut String>) {}

fn main() {
    let s = String::from("abc");
    print_type_of(&s);
    tokio::pin!(s);
    print_type_of(&s);
    // test(s); error!
    test2(s);
}

The question is not about why what Pin is or why Pin is needed.

The type of s changes from alloc::string::String to core::pin::Pin<&mut alloc::string::String> after calling tokio::pin!. How can this happen? Changing the type of the outside variable inside a macro surprises me.


Solution

  • Macros are more powerful than functions. They are evaluated at compile time, and turn into code in the same scope they are invoked. Despite that, a macro can't access variables in that scope with hardcoded names, but it can if the identifier is received as a parameter.

    Also note that Rust allows shadowing (redeclaring) variables with the same name.

    So, for example:

    let x = 42;
    
    print_type_of(x); //i32
    
    let x = "foo";
    
    print_type_of(x); //&str
    

    works just fine. The two x'es aren't really the same variable, they just have the same identifier ("name"), and the latter makes the former inaccessible.

    And a macro can emit the line that shadows the variable. So:

    macro_rules! print_and_replace {
        ($var: ident) => {
            println!("{}", $var);
            let $var = "foo";
        };
    }
    
    fn main(){
        let x = 42;
    
        print_and_replace!(x);
    
        println!("{x}");
    }
    

    is the same* as if you wrote

    fn main(){
        let x = 42;
    
        println!("{}", x);
        let x = "foo";
    
        println!("{x}");
    }
    

    *: if the identifier is passed in via an argument. It would not work if the macro tried to access x directly.


    As for why tokio::pin needs to do this, stack pinning requires that the original value is never moved again. It can ensure this by shadowing the variable, making it no longer accessible.

    So pin is not much more than this:

    macro_rules! pin {
        ($var: ident) => {
            let $var = unsafe{ Pin::new_unchecked(&mut $var) };
        }
    }