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

Bob*_*bio 5 rust

以下代码无法编译:

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);
}
Run Code Online (Sandbox Code Playgroud)

我收到以下错误:

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);
}
Run Code Online (Sandbox Code Playgroud)

我希望能够拥有一个返回抽象类的工厂,但是当该类接受引用时,我无法弄清楚如何正确指定生命周期。

krd*_*dln 7

你是对的:

引用没有理由与工厂一样长,它只需要与工厂创建的对象一样长(工厂本身不存储对字符串的引用)。

但界限call_phone说的是不同的东西

fn call_phone<'a, P: Phone, F: Factory<'a, P>>(f: F) { ... }
Run Code Online (Sandbox Code Playgroud)

该代码表示整个工厂有一个生命周期,将用于每部手机。你想要一些不同的东西,你想说这f是一个适合任何人一生的好工厂:

fn call_phone<..., F: for<'a> Factory<'a, ...>>(f: F) { ... }
Run Code Online (Sandbox Code Playgroud)

另一个问题是在Factory特征定义中:

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

Pto 的生命周期没有任何关系ms。trait 定义允许返回的手机比字符串长,这绝对应该被禁止IPhone实现!因此,为了修复它,我们向Phonetrait添加一个生命周期参数:

trait Phone<'a> {
    fn call(&self);
}
Run Code Online (Sandbox Code Playgroud)

但是还有一个问题。我们真的不能写那个签名:

fn call_phone<P: ???, F: for<'a> Factory<'a, P<'a>>(f: F) { ... }
Run Code Online (Sandbox Code Playgroud)

因为我们不想P成为一个类型,而是一个类型家族(更准确地说,是一个lifetime ? type构造函数)。请记住,每次循环迭代中的音素都有不同的类型(因为生命周期是类型的一部分,并且不同循环迭代中的生命周期是不同的)。

未来的 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);
}
Run Code Online (Sandbox Code Playgroud)

关联类型允许工厂只生产一种产品,这可能是您想要的。如果你希望不同的实现Factory有不同的Outputs,你可以通过使用幻像类型来实现:

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);
}
Run Code Online (Sandbox Code Playgroud)

Phantom对相关类型的Phone特点是不是绝对必要的,它只是扳平手机类型的假体类型,以确保需要Factory实现者是不会说谎的。