类型推断的奇怪宏行为

时间:2015-10-22 14:40:36

标签: macros rust

我正在尝试理解宏和&利用一个简单的宏来创建类似于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

1 个答案:

答案 0 :(得分:4)

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

clone()上调用&str时,它只是胖指针(两个指针大小的数字)的简单字节副本:共享引用总是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()之后会传递给clone()。在&String上调用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] 。这反过来又会出现类型不匹配错误:

&str

有一种解决方法 - 您可以直接从&String提出&*value1.to_string() ,例如明确重新借贷:

value1.to_string()

然而,现在会出现另一个问题。无论临时值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! 返回什么,仅在使用它的表达式中有效:

temporary

您可以看到insert()的调用完成时会立即销毁mips_cpu.h,并且插入的引用将变为悬空。 Rust会因编译错误而阻止这种情况。