简体   繁体   English

在Rust中使用工厂模式时指定生命周期

[英]Specifying lifetimes when using the factory pattern in Rust

The following code doesn't compile: 以下代码无法编译:

trait Phone {
    fn call(&self);
}

struct IPhone<'a> {
    my_str: &'a str
}

impl<'a> Phone for IPhone<'a> {
    fn call(&self) {
        print!("{}", self.my_str);
    }
}

trait Factory<'a, P: Phone> {
    fn new_phone(&self, ms: &'a str) -> P;
}

struct IPhoneFactory;
impl<'a> Factory<'a, IPhone<'a>> for IPhoneFactory {
    fn new_phone(&self, ms: &'a str) -> IPhone<'a> {
        return IPhone {
            my_str: ms
        };
    }
}

fn call_phone<'a, P: Phone, F: Factory<'a, P>>(f: F) {
    for _ in 0..10 {
        let s = String::new();
        let p = f.new_phone(s.as_str());
        p.call();
    }
}

fn main() {
    call_phone(IPhoneFactory);
}

I get the following error: 我收到以下错误:

error: `s` does not live long enough
        let p = f.new_phone(s.as_str());
                            ^
note: reference must be valid for the lifetime 'a as defined on the block at 28:53...
 fn call_phone<'a, P: Phone, F: Factory<'a, P>>(f: F) {
                                                      ^
note: ...but borrowed value is only valid for the block suffix following statement 0 at 30:30
        let s = String::new();
                              ^

I want to be able to have a factory that returns an abstract class, but when that class takes a reference I can't figure out how to specify the lifetime properly. 我希望能够有一个返回抽象类的工厂,但是当该类接受引用时,我无法弄清楚如何正确地指定生命周期。

You're right about that: 你是对的:

There is no reason for the reference to live as long as the factory, it only needs to live as long as the object the factory is creating (the factory itself doesn't store a reference to the string). 没有理由只要工厂存在引用,它只需要工厂生成的对象就可以存活(工厂本身不存储对字符串的引用)。

But the bound on call_phone says something different 但是call_phone上的call_phone说明了一些不同的东西

fn call_phone<'a, P: Phone, F: Factory<'a, P>>(f: F) { ... }

That code says that there's a single lifetime for the whole factory, which will be used for each phone . 该代码表示​​整个工厂只有一个生命周期 ,将用于每部手机 You want something different, you want to say that f is a good factory for any lifetime : 你想要不同的东西,你想说f对于任何一生来说都是一个好工厂:

fn call_phone<..., F: for<'a> Factory<'a, ...>>(f: F) { ... }

The other problem is that in the Factory trait definition: 另一个问题是在Factory特征定义中:

trait Factory<'a, P: Phone> {
    fn new_phone(&self, ms: &'a str) -> P;
}

There's nothing tying lifetime of P to ms . Pms生命周期没有任何关系。 The trait definition allows the returned phone to outlive the string, which should definitely be forbidden for the IPhone implementation! 特征定义允许返回的电话比字符串更长,这绝对不应该被IPhone实现! So, to fix it, we add a lifetime parameter to the Phone trait: 因此,为了解决这个问题,我们在Phone特性中添加了一个生命周期参数:

trait Phone<'a> {
    fn call(&self);
}

But there's still one problem. 但仍有一个问题。 We can't really write that signature: 我们不能写那个签名:

fn call_phone<P: ???, F: for<'a> Factory<'a, P<'a>>(f: F) { ... }

Since we want P to be not a type, but rather a family of types (more precisely, a lifetime → type constructor). 因为我们希望P不是一个类型,而是一个类型族(更准确地说,是一个lifetime → type构造函数)。 Remember, the phone in each iteration of loop has a different type (since the lifetime is a part of a type, and lifetimes in different iterations of loops are different). 请记住,循环的每次迭代中的电话具有不同的类型(因为生命周期是类型的一部分,并且循环的不同迭代中的生命期是不同的)。

Ability to express such a signature is planned for the future Rust, but for now, we have to make a workaround and make the phone associated type of Factory trait: 表示这样的签名的能力计划用于未来的Rust,但是现在,我们必须制定解决方法并使手机相关类型的Factory特性:

trait Phone<'a> {
    fn call(&self);
}

struct IPhone<'a> {
    my_str: &'a str
}

impl<'a> Phone<'a> for IPhone<'a> {
    fn call(&self) {
        println!("{}", self.my_str);
    }
}

trait Factory<'a> {
    type Output: Phone<'a>;
    fn new_phone(&self, ms: &'a str) -> Self::Output;
}

struct IPhoneFactory;
impl<'a> Factory<'a> for IPhoneFactory {
    type Output = IPhone<'a>;
    fn new_phone(&self, ms: &'a str) -> IPhone<'a> {
        IPhone {
            my_str: ms
        }
    }
}

fn call_phone<F: for<'a> Factory<'a>>(f: F) {
    for i in 0..10 {
        let s = i.to_string();
        let p = f.new_phone(&s);
        p.call();
    }
}

fn main() {
    call_phone(IPhoneFactory);
}

Associated type allows the factory to produce only one kind of product, which is maybe what you wanted. 相关类型允许工厂只生产一种产品,这可能是你想要的。 If you want different implementations of Factory to have different Output s, you can achieve this by using phantom types: 如果您希望Factory不同实现具有不同的Output ,则可以使用幻像类型来实现:

trait Phone<'a> {
    type Phantom;
    fn call(&self);
}

enum IPhonePhantom {}

struct IPhone<'a> {
    my_str: &'a str
}

impl<'a> Phone<'a> for IPhone<'a> {
    type Phantom = IPhonePhantom;
    fn call(&self) {
        println!("{}", self.my_str);
    }
}

trait Factory<'a, Selector> {
    type Output: Phone<'a, Phantom=Selector>;
    fn new_phone(&self, ms: &'a str) -> Self::Output;
}

struct MyFactory;
impl<'a> Factory<'a, IPhonePhantom> for MyFactory {
    type Output = IPhone<'a>;
    fn new_phone(&self, ms: &'a str) -> IPhone<'a> {
        IPhone {
            my_str: ms
        }
    }
}

fn call_phone<Selector, F: for<'a> Factory<'a, Selector>>(f: F) {
    for i in 0..10 {
        let s = i.to_string();
        let p = f.new_phone(&s);
        p.call();
    }
}

fn main() {
    call_phone::<IPhonePhantom, _>(MyFactory);
}

The Phantom associated type on the Phone trait is not strictly necessary, it's only needed to tie the phone type to its phantom type and to make sure Factory implementors don't lie. Phone特性上的Phantom相关类型并不是绝对必要的,只需要将电话类型与其幻像类型联系起来,并确保Factory实现者不会撒谎。

Your problem is here: 你的问题在这里:

fn call_phone<'a, P: Phone, F: Factory<'a, P>>(f: F) {
// Factory has a lifetime 'a ----------^
// that is at least as long as the scope of call_phone
    for _ in 0..10 {
        let s = String::new(); // s is born here
        let p = f.new_phone(s.as_str());
        // new reference ---^   
        // new_phone definition requires this to have
        // the same lifetime 'a as the Factory          
        p.call();
    } 
    // s is destroyed here, no references to s can 
    // still exist
} // F is still alive

One thing you can do is passing the &str as a parameter to call_phone , to make sure the reference lives as long as the function: 你可以做的一件事是将&str作为参数传递给call_phone ,以确保引用的存在时间与函数一样长:

fn call_phone<'a, P: Phone, F: Factory<'a, P>>(f: F, s: &'a str) {
    for _ in 0..10 {
        let p = f.new_phone(s);
        p.call();
    }
}

fn main() {
    call_phone(IPhoneFactory, &"hello");
}

Another one is not working with references, but let your struct IPhone own the String 另一个不使用引用,但让你的struct IPhone拥有String

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM