工厂方法:实例寿命不够长

Factory method: instance does not live long enough

我正在用 Rust 开发一个单词生成器。该应用程序由两个主要结构组成:LetterAlphabet.

Letter 由单个字符和有关其与其他字母关系的规则组成。

Alphabet 包含元音和辅音的向量以及引用这些向量中的字母的哈希图。这样做是为了在 O(1) 时间内检索字母的规则。

我创建了一个工厂方法来从 json 字符串(下面的代码)中读取字母表,但是我收到一个错误,指出字母表实例的寿命不够长。

src/word_generator/alphabet.rs:47:6: 47:14 error: alphabet does not live long enough src/word_generator/alphabet.rs:47 alphabet.add_letter(letter);

src/word_generator/alphabet.rs:26:40: 55:3 note: reference must be valid for the anonymous lifetime #1 defined on the block at 26:39... src/word_generator/alphabet.rs:26 pub fn from_json(json: &str)->Alphabet{

note: ...but borrowed value is only valid for the block suffix following statement 3 at 40:37 src/word_generator/alphabet.rs:40 let mut alphabet = Alphabet::new(); src/word_generator/alphabet.rs:41

我理解这个错误(我希望如此),但我不明白为什么会这样。为什么函数new()返回的Alphabet的实例被变量alphabet借用了?这不是移动操作吗?

pub struct Alphabet<'a>{
    pub vowels: Vec<Letter>,
    pub consonants: Vec<Letter>,
    letters: HashMap<char,&'a Letter>
}

impl<'a> Alphabet<'a>{

    pub fn new()->Alphabet<'a>{

        return Alphabet{
            vowels: Vec::new(),
            consonants: Vec::new(),
            letters: HashMap::new()
        }

    }

    pub fn from_json(json: &str)->Alphabet{

        let data :Json = match Json::from_str(json){
            Ok(_data)=>_data,
            Err(_err)=>panic!("Invalid JSON provided")
        };

        let letters = match data.as_array(){
            Some(_letters)=>_letters,
            None=>panic!("Expected JSON\'s root to be an array but found a different structure.")
        };

        let mut it = letters.iter();

        let mut alphabet = Alphabet::new();

        loop {
            match it.next(){
                Some(x) =>{

                    let letter : Letter= json::decode(&(x.to_string())).unwrap();
                    alphabet.add_letter(letter);

                },
                None => break,
            }
        }

        return alphabet
    }

    fn add_letter(&'a mut self,ref l: Letter){

        match l.letter_type {
            LetterType::Vowel =>{
                self.vowels.push(l.clone());
                self.letters.insert(l.value, &self.vowels.last().unwrap());
            },
            LetterType::Consonant =>{ 
                self.consonants.push(l.clone());
                self.letters.insert(l.value, &self.consonants.last().unwrap());
            }
        }

    }
}

P.S.: 我是 Rust 的新手,所以欢迎任何改进代码的建议。

这是一个可能是您开始的小示例:

use std::collections::HashMap;

struct Letter;

struct Alphabet<'a>{
    vowels: Vec<Letter>,
    letters: HashMap<u8, &'a Letter>
}

impl<'a> Alphabet<'a> {
    fn add(&mut self, l: Letter) {
        self.vowels.push(l);
        self.letters.insert(42, &self.vowels.last().unwrap());
    }
}

fn main() {}

你接着编译错误1:

<anon>:12:46: 12:52 error: cannot infer an appropriate lifetime for lifetime parameter 'a in function call due to conflicting requirements
<anon>:12         self.letters.insert(42, &self.vowels.last().unwrap());
                                                       ^~~~~~
<anon>:10:5: 13:6 help: consider using an explicit lifetime parameter as shown: fn add(&'a mut self, l: Letter)

然后继续,直到你得到这样的东西:

use std::collections::HashMap;

struct Letter;

struct Alphabet<'a>{
    vowels: Vec<Letter>,
    letters: HashMap<u8, &'a Letter>
}

impl<'a> Alphabet<'a> {
    fn new() -> Alphabet<'a> {
        Alphabet { vowels: Vec::new(), letters: HashMap::new() }
    }

    fn add(&'a mut self, l: Letter) {
        self.vowels.push(l);
        self.letters.insert(42, &self.vowels.last().unwrap());
    }

    fn parse() -> Alphabet<'a> {
        let mut a = Alphabet::new();
        a.add(Letter);
        a
    }
}

fn main() {}

根本问题在于2。在一般情况下,无论何时移动结构,所有成员变量的内存位置都会改变,从而使所有引用失效。这是一件坏事,Rust 会阻止你。

您收到的错误消息指出没有可能的生命周期可以满足您的要求 — 引用仅在结构不移动时有效,但您希望 return 从方法,移动它

"But wait!"你说,"I have a Vec and the contents of the Vec are on the heap and won't move!"。虽然技术上是真实的(最好的真实),但 Rust 不会在那么细粒度的级别上跟踪事物。

这里的一般解决方案是将您的结构分成两部分。将 JSON 中的所有内容解析为仅包含 VecAlphabet 结构。然后将该结构(可能通过引用,也可能通过值)传递给 AlphabetSoup 结构。该结构可以一次性创建 HashMap 并提供一个位置来缓存您的值。

1 较新的编译器实际上 删除了 这个建议,因为误报率太高了高,它带来的混乱多于帮助。

2 你实际上 可以 引用你自己的成员,但你可以永远不要移动对象,这使得它在大多数情况下不切实际。