`tokio::pin` 如何改变变量的类型?

huangapple go评论66阅读模式
英文:

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

问题

s 的类型从 alloc::string::String 更改为 core::pin::Pin<&mut alloc::string::String> 是通过调用 tokio::pin! 宏实现的。在宏的作用下,对 s 进行了 Pin 包装,改变了其类型。在宏内部,可能使用了一些特殊的技术或方法来实现这种类型的转换。

英文:
use std::pin::Pin;

fn print_type_of&lt;T&gt;(_: &amp;T) {
    println!(&quot;{}&quot;, std::any::type_name::&lt;T&gt;())
}

fn test(s: String) {}
fn test2(s: Pin&lt;&amp;mut String&gt;) {}

fn main() {
    let s = String::from(&quot;abc&quot;);
    print_type_of(&amp;s);
    tokio::pin!(s);
    print_type_of(&amp;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&lt;&amp;mut alloc::string::String&gt; after calling tokio::pin!. How can this happen? Changing the type of the outside variable inside a macro surprises me.

答案1

得分: 4

宏比函数更强大。它们在编译时进行评估,并在被调用的相同作用域内转换为代码。尽管如此,宏无法使用硬编码的名称访问该作用域中的变量,但如果标识符作为参数接收,它就可以这样做。

此外,请注意,Rust 允许使用相同名称重新声明变量。

所以,例如:

let x = 42;

print_type_of(x); //i32

let x = "foo";

print_type_of(x); //&str

完全可以工作。这两个 x 实际上不是同一个变量,它们只是具有相同的标识符("name"),后者使前者无法访问。

可以生成遮蔽变量的代码行。所以:

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

fn main(){
    let x = 42;

    print_and_replace!(x);

    println!("{}", x);
}

与您编写的代码相同,如果标识符通过参数传递。如果宏尝试直接访问 x,则不起作用。

关于为什么 tokio::pin 需要这样做,栈固定需要确保原始值不会再次移动。通过遮蔽变量,它可以确保这一点,使其不再可访问。

所以 pin 不过是这样的:

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

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 = &quot;foo&quot;;

print_type_of(x); //&amp;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) =&gt; {
        println!(&quot;{}&quot;, $var);
        let $var = &quot;foo&quot;;
    };
}

fn main(){
    let x = 42;

    print_and_replace!(x);

    println!(&quot;{x}&quot;);
}

is the same* as if you wrote

fn main(){
    let x = 42;

    println!(&quot;{}&quot;, x);
    let x = &quot;foo&quot;;

    println!(&quot;{x}&quot;);
}

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


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) =&gt; {
        let $var = unsafe{ Pin::new_unchecked(&amp;mut $var) };
    }
}

huangapple
  • 本文由 发表于 2023年6月12日 12:51:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/76453726.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定