类型推断的奇怪宏行为

Weird macro behavior for type inference

我正在尝试理解宏并利用一个简单的宏来创建类似于 vec! 的哈希图。出于某种原因,如果我在宏中将 String 转换为 &str,它似乎不起作用。

macro_rules! hashmap {
    ($( $key: expr => $val: expr ),*) => {{
         let mut map = ::std::collections::HashMap::new();
         $( map.insert($key.clone(), $val.clone()); )*
         map
    }}
}

fn works(){
    let value1 = 5;
    let value1str: &str = &value1.to_string();
    let h = hashmap!["key0"   => "value0"
                     , "key1" => value1str];
    println!("{:?}", h);
}

// fn does_not_work(){
//     let value1 = 5;
//     let h = hashmap!["key0"   => "value0"
//                      , "key1" => &value1.to_string()];
//     println!("{:?}", h);
// }

fn main(){
    works();
    //does_not_work();
}

Example on playpen

这是您的 works() 函数的有效主体:

fn works(){
    let value1 = 5;
    let value1str: &str = &value1.to_string();
    let h = {
        let mut map = HashMap::new();
        map.insert("key0".clone(), "value0".clone());
        map.insert("key1".clone(), value1str.clone());
        map
    };
    println!("{:?}", h);
}

还有 does_not_work() 中的这个:

fn does_not_work(){
    let value1 = 5;
    let h = {
        let mut map = HashMap::new();
        map.insert("key0".clone(), "value0".clone());
        map.insert("key1".clone(), (&value1.to_string()).clone());
        map
    };
    println!("{:?}", h);
}

当在 &str 上调用 clone() 时,它只是一个简单的按字节复制的胖指针(两个指针大小的数字):共享引用总是 Copy;当然,克隆的引用也与原始引用具有相同的生命周期。这在 works() 中是可以的,因为那里使用的三个字符串切片指向静态内存,因为它们是字符串文字,第四个指向一个临时局部变量,该变量一直存在到函数结束:

let value1str: &str = &value1.to_string();
// is in fact equivalent to
let temporary = value1.to_string();
let value1str: &str = &temporary;

因为这个 temporary 比散列映射的寿命长,所以可以将对它的引用存储在这个散列映射中:映射首先被销毁并且永远没有机会持有悬空引用。

然而,在第二种情况下,clone() 是在 &String 类型的值上调用的,因为 &s 如果 s: String 会给你 &str仅当编译器知道 &str 应该是目标类型时,情况并非如此,因为 &value1.to_string() 之后传递给 clone()。当在 &String 上调用 clone() 时,它自然会委托给 impl Clone for String,后者只是创建另一个拥有的字符串副本,从而为您提供 String。这反过来会给你一个类型不匹配错误:

<anon>:20:34: 4:50 error: mismatched types:
 expected `&str`,
    found `collections::string::String`
(expected &-ptr,
    found struct `collections::string::String`) [E0308]

有一种方法可以解决它 - 您可以直接从 &String 中询问 &str,例如明确再借:

&*value1.to_string()

但是,现在又会出现一个问题。任何临时值 value1.to_string() returns 仅在使用它的表达式中有效:

map.insert("key1".clone(), (&*value1.to_string()).clone());
// is roughly equivalent to
{
    let temporary = value1.to_string();
    map.insert("key1".clone(), (&*temporary).clone());
}  // these braces are important!

你可以看到当insert()调用完成时temporary会立即被销毁,插入的引用会变成悬空。 Rust 通过编译器错误来防止这种情况。