Saddle Point
Saddle Point

Reputation: 3225

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.

Upvotes: 3

Views: 538

Answers (1)

FZs
FZs

Reputation: 18619

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) };
    }
}

Upvotes: 5

Related Questions